元素个数是动态的,就应使用集合类。大多数集合接口都可在System.Collections和System.Collections.Generie名称空间中找到。
详细可见:C# 集合(Collection) | 菜鸟教程 (runoob.com)
1. 列表(List<T>)
动态列表使用泛型类List<T>,元素个数是动态的,相当于数组的泛型类集合
1. 在泛型类List<T>中,必须为声明为列表的值指定类型(T必须有具体的类型,比如int,string等)
2. ArrayList是一个非泛型列表,它可以添加任意object类型作为其元素(比如int, string等),是动态数组(用法和List<T>相似)。动态数组(ArrayList)代表了可被单独索引的对象的有序集合。它基本上可以替代一个数组。但是,与数组不同的是,您可以使用索引在指定的位置添加和移除项目,动态数组会自动重新调整它的大小。它也允许在列表中进行动态内存分配、增加、搜索、排序各项。
3. 只读集合:AsReadOnly()集合不能修改
//新建一个int类型的List
var intList = new List<int>();
var racList1 = new List<Racer>(10);
var intList2 = new List<int>() { 1,2,8};
//Add()方法添加一个元素
intList.Add(5);
racList1.Add(new Racer("APPLE", 3, 'F'));
//AddRange()一次给集合添加多个元素,可以传递一个数组
racList1.AddRange(new Racer[] { new Racer("APPLE", 3, 'F'),
new Racer("blue", 5, 'M')});
//Insert()方法可以在指定位置插入元素
racList1.Insert(2, new Racer("blue", 5, 'M'));
//InsertRange()插入大量元素
racList1.InsertRange(3, new Racer[] { new Racer("APPLE", 3, 'F'),
new Racer("blue", 5, 'M')});
//访问元素,可以通过元素号
Racer rac1 = racList1[2];
for(int i = 0; i < racList1.Count(); i++)
{
Console.WriteLine(racList1[i]);
}
foreach(var r in racList1)
{
Console.WriteLine(r.name);
}
//删除元素,可以利用索引,也可以传递要删除的元素
racList1.RemoveAt(3);
racList1.Remove(racList1[1]);
racList1.RemoveRange(4, 1);
// 搜索:IndexOf(), LastIndexOf(),FindIndex(),FindLastIndex(),Find()和FindLast()
//排序: Sort()使用快速排序算法,比较所有元素
//使用Capacity可以获取和设置集合的容量
intList.Capacity = 20;
ArrayList al = new ArrayList();
al.Add(45);
al.Sort();
2. 队列(Queue)
queue队列先进先出,Enqueue()从队尾进队,Dequeue()从队头出队
Queue q = new Queue();
q.Enqueue('A');
q.Enqueue('b');
q.Enqueue('b');
//不能指定哪个出队,只能默认从队头出队,每用一次Dequeue()就会出队一个元素
q.Dequeue();
3. 栈
stack后进先出,Push()在栈中添加元素,Pop()获取最近添加的元素出栈
Stack st = new Stack();
st.Push("A");
st.Push("B");
st.Push("C");
//栈不能指定哪个元素出栈,只能每次出栈刚进栈的元素
st.Pop();
4. 链表
LinkedList<T>是一个双向链表,其元素指向它前面和后面的元素。
1. 链表优点:元素插入迅速
2. 链表缺点:元素只能一个接一个的访问,查找元素用时长
详细请见:C#集合之链表 - Ruby_Lu - 博客园 (cnblogs.com)
5. 有序列表
SortedList<TKey, TValue>按照键(TKey)给元素排序,这些键值对可以通过键和索引来访问
1. 只允许每个键有一个对应的值
2. TKey,和TValue可以使用任意类型
3. 有序列表在添加元素时就会自动的进行排序
4. 排序列表是数组和哈希表的组合。它包含一个可使用键或索引访问各项的列表。如果您使用索引访问各项,则它是一个动态数组(ArrayList),如果您使用键访问各项,则它是一个哈希表(Hashtable)。集合中的各项总是按键值排序。
var sortlist = new SortedList<int, string>();
sortlist.Add(1, "aaa");
sortlist.Add(2, "bbb");
//新添加一个元素
sortlist[8] = "aaabbb";
//存在的键,会将原先的值覆盖
sortlist[2] = "888";
//键的索引可以取值
var data = sortlist[2];
//所有的键
var keys = sortlist.Keys;
//所有的值
var values = sortlist.Values;
6. 哈希表(Hashtable)
Hashtable 类代表了一系列基于键的哈希代码组织起来的键/值对。它使用键来访问集合中的元素。
1. 当您使用键访问元素时,则使用哈希表,而且您可以识别一个有用的键值。哈希表中的每一项都有一个键/值对。键用于访问集合中的项目。
Hashtable ht = new Hashtable();
ht.Add("001", "Zara Ali");
if (ht.ContainsValue("Nuha Ali"))
{
Console.WriteLine("This student name is already in the list");
}
else
{
ht.Add("008", "Nuha Ali");
}
// 获取键的集合
ICollection key = ht.Keys;
foreach (string k in key)
{
Console.WriteLine(k + ": " + ht[k]);
}
7. 字典
7.1 Dictionary<TKey, TValue>
Dictionary<TKey, TValue>允许按照某个键来访问元素,字典也称为映射或散列表
1. 根据TKey键值快速查找TValue值,也可以自由地添加和删除元素,但没有在内存中移动后续元素的性能开销
2. 任何键都必须是唯一的,不能为空引用null,若值为引用类型,则可以为空值。
3. Key和Value可以是任何类型(string,int,自定义类等)
//创建及初始化
Dictionary<int, string> myDictionary = new Dictionary<int, string>();
//添加元素
myDictionary.Add(1, "aaa");
myDictionary.Add(2, "bbb");
myDictionary.Add(3, "ccc");
myDictionary.Add(4, "ddd");
//通过Key查找元素
if (myDictionary.ContainsKey(1))
{
Console.WriteLine("Key:{0},Value:{1}", "1", myDictionary[1]);
}
//通过KeyValuePair遍历元素
foreach (KeyValuePair<int, string> kvp in myDictionary)
{
Console.WriteLine("Key = {0}, Value = {1}", kvp.Key, kvp.Value);
}
//键 Keys
var key = myDictionary.Keys;
//值 Valus
var values = myDictionary.Values;
//Remove方法移除指定的键值
myDictionary.Remove(2);
7.2 Lookup<TKey, TValue>
Lookup<TKey, TValue>把键映射到一个值集合上,一个TKey对应多个TValue
1. Lookup<TKey, TValue>不能像一般的字典那样创建,而必须调用ToLookup()方法
var racList1 = new List<Racer>(10);
racList1.Add(new Racer("APPLE", 3, 'F'));
//AddRange()一次给集合添加多个元素,可以传递一个数组
racList1.AddRange(new Racer[] { new Racer("APPLE", 3, 'F'),
new Racer("blue", 5, 'M')});
var lookuprac = racList1.ToLookup(r => r.age);
//lookuprac[5]的方括号中的值要和ToLookup(r => r.age)中r用的值一样
foreach (Racer r in lookuprac[5])
{
Console.WriteLine(r);
}
7.3 有序字典
SortedDictionary<TKey, TValue>是一个二叉搜索树,元素根据键来排序。和字典使用大致一样,只不过有序字典会自动排序,字典不会排序
1. 和字典一样,键唯一
2. SortedList<Tkey,Tvalue>类使用的内存比SortedDictionary<Tkey,Tvalue>类少
3. SortedList<Tkey,Tvalue>类使用的内存比SortedDictionary<Tkey,Tvalue>类少
8.集
集:包含不重复元素的集合(集只包含唯一值)。HashSet<T>(包含不重复元素的无序列表)和SortedSet<T>(包含不重复元素的有序列表)
1. 主要被设计用来存储集合,做高性能集运算,例如两个集合求交集、并集、差集等
//创建无序集HashSet,不能有重复否则会合并
var myHashSet = new HashSet<int>() { 1, 2, 1, 4, 1, 6, 4, 6, 6, 6 };
var myHashSet1 = new HashSet<int>() { 1, 2 };
foreach (int i in myHashSet) //输出1246
{
Console.Write(" " + i);
}
//创建有序集SortedSet,不能有重复,否则会合并
var mySortedSet = new SortedSet<int>() { 7, 8, 9, 4, 1, 6 };
//添加元素,如果添加的元素存在的话,忽略
myHashSet.Add(1);
mySortedSet.Add(1);
判断集是否为子集,返回bool值
myHashSet1.IsSubsetOf(myHashSet);
判断集是否为超集,返回bool值
myHashSet.IsSupersetOf(myHashSet1);
判断是否有交集,返回bool值
myHashSet1.Overlaps(myHashSet1);
并集
var myHashSet3 = new HashSet<int>(myHashSet1);
myHashSet3.UnionWith(myHashSet1);
将子集删除
myHashSet3.ExceptWith(myHashSet1);//删除子集
9. 各集合性能(时间复杂度)
10. 处理位(BitArray, BitVector32)
BitArray类可以重新设置大小,事先不知道位数时,则使用BitArray类,它可以包含非常多的位。
BitVector32结构是基于栈的,因此比较快,BitVector32结构仅包含32位,它们存储在一个整数中
10.1 BitArray
BitArray 类管理一个紧凑型的位值数组,它使用布尔值来表示,其中 true 表示位是开启的(1),false 表示位是关闭的(0)。
1. 当您需要存储位,但是事先不知道位数时,则使用点阵列(BitArray )。
2. 可使用整型索引从点阵列集合中访问各项,索引从零开始。
3. BitArray是引用类型
// 创建两个大小为 8 的点阵列
BitArray ba1 = new BitArray(8);
BitArray ba2 = new BitArray(8);
byte[] a = { 60 };
byte[] b = { 13 };
// 把值 60 和 13 存储到点阵列中
ba1 = new BitArray(a);
ba2 = new BitArray(b);
// ba1 的内容
Console.WriteLine("Bit array ba1: 60");
for (int i = 0; i < ba1.Count; i++)
{
Console.Write("{0, -6} ", ba1[i]);
}
Console.WriteLine();
// ba2 的内容
Console.WriteLine("Bit array ba2: 13");
for (int i = 0; i < ba2.Count; i++)
{
Console.Write("{0, -6} ", ba2[i]);
}
Console.WriteLine();
BitArray ba3 = new BitArray(8);
//位的和操作
ba3 = ba1.And(ba2);
// ba3 的内容
Console.WriteLine("Bit array ba3 after AND operation: 12");
for (int i = 0; i < ba3.Count; i++)
{
Console.Write("{0, -6} ", ba3[i]);
}
Console.WriteLine();
//位的或操作
ba3 = ba1.Or(ba2);
// ba3 的内容
Console.WriteLine("Bit array ba3 after OR operation: 61");
for (int i = 0; i < ba3.Count; i++)
{
Console.Write("{0, -6} ", ba3[i]);
}
10.2 BitVector32
如果事先知道需要的位数,就可以使用BitVector32替代BitArray。
1. BitVector32是一个值类型,在整数栈上存储位,一个整数可以存储32位
//新建
var bits = new BitVector32(0X23); //新建并赋值0x23,不设置默认为0
//输出bits:00000000000000000000000000100011
Console.WriteLine(bits.ToString());
//掩码读取位
//所谓掩码就是用0屏蔽不关心的位,用1凸显关心的位
Console.WriteLine(bits[0]); //
Console.WriteLine(bits[1]); //读取第1位->1-true
Console.WriteLine(bits[2]); //读取第2位->1-true
Console.WriteLine(bits[3]); //读取第1位和第2位->1、1-true
Console.WriteLine(bits[4]); //读取第3位->0-false
Console.WriteLine(bits[5]); //读取第1位和第3位->0、1->false
Console.WriteLine(bits[0x23]); //读取第1、2、6位->1、1、1->true
Console.WriteLine(bits[0x13]); //读第1、2、5位->1、1、0->false
//综上所列,读取的掩码位数为1的话,就返回该位的值,
//读取的掩码为1位以上的话,例如3、5、0x13\0x23的话,就返回对应位值的相与结果
//创建要读取的掩码,实际上就是从1开始的2倍数列。1、2、4、8、16、32。。。
int bit1 = BitVector32.CreateMask(); //创建掩码1、0001
int bit2 = BitVector32.CreateMask(bit1); //创建掩码2、0010
int bit3 = BitVector32.CreateMask(bit2); //创建掩码4、0100
int bit4 = BitVector32.CreateMask(bit3); //创建掩码8、1000
int bit5 = BitVector32.CreateMask(bit4); //创建掩码16、10000
bits[bit1] = true; //通过掩码设置第1位
bits[bit2] = false;//通过掩码设置第2位
bits[bit3] = true; //通过掩码设置第3位
bits[bit4] = false;//通过掩码设置第4位
bits[bit5] = true; //通过掩码设置第5位
Console.WriteLine(bits);
//创建多位掩码
//0xabcdef=00000000101010111100110111101111
bits[0xabcddf] = true;
Console.WriteLine(bits); //00000000101010111100110111111111
//创建片段,可以将32个位按照需求分为多个片段,这样便可以访问具体片段的值
/*
* 下面参数中第一个参数oxfff指的是当前片段所要存储的最大数据。其目的有两个
* 1、限制片段的最大值,保证存储数据的规范
* 2、根据最大值可以分配合适的最小空间
* 例如0xfff表示111111111111它就表示要占用12个位,所以片段划分就是12个位
*/
BitVector32.Section sectionA = BitVector32.CreateSection(0xfff); //创建片段
/*
* 相同的原理0xff表示11111111,共需要8个位,所以该片段分配8个位
*/
BitVector32.Section sectionB = BitVector32.CreateSection(0xff, sectionA); //创建片段,占8位
BitVector32.Section sectionC = BitVector32.CreateSection(0xf, sectionB); //创建片段,占4位
BitVector32.Section sectionD = BitVector32.CreateSection(0x7, sectionC); //创建片段,占3位
BitVector32.Section sectionE = BitVector32.CreateSection(0x7, sectionD); //创建片段,占3位
BitVector32.Section sectionF = BitVector32.CreateSection(0x3, sectionE); //创建片段,占2位
//此时bits:00 000 000 1010 10111100 110111111111
//输出每个
Console.WriteLine(sectionA); //输出Section{0xfff,0x0}
Console.WriteLine(bits[sectionA]); //3583->110111111111
Console.WriteLine(bits[sectionB]); //188->10111100
Console.WriteLine(bits[sectionC]); //10->1010
Console.WriteLine(bits[sectionD]); //0->000
Console.WriteLine(bits[sectionE]); //0->000
Console.WriteLine(bits[sectionF]); //0->00
11. 可观察的集合
ObservableCollection<T>:如果需要集合中的元素何时删除或添加信息
1. 动态数据集合并且当集合中新增、修改或者删除项目时,或者集合被刷新时,都有通知机制(通过实现接口INotifyCollectionChanged)
2. INotifyCollectionChanged它不是集合,只是一个接口。在类中提供一个事件PropertyChanged,当属性的值发生改变时通知客户端。如果对象的状态发生改变时(新增、修改、删除)将触发事件PropertyChange指向那些已经发生改变的集合
3. WPF 提供 ObservableCollection 类,它是实现 INotifyCollectionChanged 接口的数据集合的内置实现
4. 用法和List<T>类似
12. 不变的集合
不能改变的集合称为不变的集合。如果对象改变其状态,就很难在多个同时运行的任务中使用,这些集合必须同步。如果对象不能改变其状态,就很容易在多个线程中使用
1. 每次变化之后会生成一个新集合,同步所有多线程
2. 并不是真的一有变化就生成新的对象,在使用不变集合的每一个阶段,都没有复制完整的集合,而是使用了共享状态,仅在需要时复制集合。
使用静态的Create方法创建该数组,Create方法被重载,可以传递任意数量的元素
ImmutableArray<string> arr = ImmutableArray.Create<string>();
Add 方法不会改变不变集合本身,而是返回一个新的不变集合
ImmutableArray<string> arr1 = arr.Add("one");
//可以一次调用多个Add方法
ImmutableArray<string> arr2 = arr1.Add("two").Add("three");
foreach (string i in arr2)
{
Console.WriteLine(i);
}
13 并发集合
详细请见转载于:
C#并发集合 - HackerVirus - 博客园 (cnblogs.com)