集合⚓︎
一、集合概述⚓︎
对于许多应用程序,你会想要创建和管理相关对象的组。 有两种方法对对象进行分组:通过创建对象的数组,以及通过创建对象的集合。
数组最适用于创建和使用固定数量的强类型化对象。与数组不同,集合提供更灵活的方式来使用对象组,你使用的对象组随着应用程序更改的需要动态地放大和缩小。
集合是专门被设计用来存储和检索数据的类,这些类是 .NET 框架提供的,System.Collection
命名空间下提供了一些集合类,但它们不是类型安全的,该命名空间中的类不会将元素作为特别类型化的对象存储,而是作为 Object
类型的对象存储。
随着版本迭代,.NET 提供了泛型集合,位于 System.Collection.Generic
命名空间下, 泛型集合强制类型安全,因此无法向其添加任何其他数据类型。 当你从泛型集合检索元素时,你无需确定其数据类型或对其进行转换。
为了实现多线程下的安全访问,System.Collection.Concurrent
命名空间下提供了并发安全的集合类。
Warning
应尽可能地使用 System.Collections.Generic
命名空间或 System.Collections.Concurrent
命名空间中的泛型集合,而不是 System.Collections
命名空间中的旧类型。
本节内容将介绍一些常用的泛型集合类以及简单使用,对于非泛型集合类作了解补充。主要涉及的泛型类有:
类 | 说明 |
---|---|
List<T> | 列表,自适应大小的数组集合 |
Dictionary<K,V> | 键值集合,按键值对存储的集合 |
HashSet<T> | 集,不允许重复元素的数组集合 |
SortedList<K,V> | 排序键值列表,根据自定义排序规则实现按键排序的键值对集合 |
Stack<T> | 栈,先进后出的数据结构 |
Queue<T> | 队列,先进先出的数据结构 |
BitArray | 点列阵,存储二进制的紧缩数组 |
二、列表⚓︎
数组(array)在声明时通常会指定大小,而一旦确定后,数组大小就无法改变了。为了满足更加灵活的应用场景,因此定义了动态数组,也可称列表(List)。List 是可以自动调整大小的数据结构,并且允许在列表中进行动态内存分配、增加、搜索、排序等操作。
.NET类⚓︎
List<T>
是 .NET System.Collections.Generic 中提供的类型安全的泛型列表,它替代了早期版本 System.Collections 中定义的 ArrayList
,当你需要使用动态容量的集合存储数据时,你可以考虑它:
- 当存储的对象具有同质类型时,应当使用
List<T>
,T
为同质类型; - 当存储的对象具有不同类型时,应当使用
List<Object>
,Object
是所有类型的基类 。
Note
List<T> 具有以下特点:
元素无序性。存储的元素是无序的,它们只按添加的顺序指派索引,需要排序输出请使用Sort方法。
允许存储重复元素。列表允许重复添加同一元素。
允许存储 null 。列表允许添加
null
,并且允许多次添加。
非线程安全的。执行多次读取操作是安全的,但如果在读取时修改集合,则会出现问题。
Danger
为了确保线程安全,在读或写操作期间锁定集合。要使一个集合能够被多个线程访问以进行读写,您必须实现自己的同步。对于内置同步的集合,请参阅 System.Collections.Concurrent 命名空间中的类。有关固有的线程安全替代方法,请参阅 ImmutableList<T>类。
继承体系:⚓︎
基本使用⚓︎
/* 定义一个方法用来输出 */
private static void Print(string log, List<string> list)
{
Console.Write(log + ":");
list.ForEach(x => Console.Write(x));
Console.WriteLine();
}
private static void Main(string[] args)
{
// 实例化
List<string> list = new List<string>();
// 添加
list.Add("A");
list.Add("B");
list.Add("C");
list.Add("D");
list.Add("E");
list.Add("F");
list.Add("G");
// 属性
Console.WriteLine(list.Capacity);
Console.WriteLine(list.Count);
// 遍历
Console.Write("遍历:");
foreach (String s in list)
Console.Write(s);
Console.WriteLine();
// 索引
Console.WriteLine("索引:" + list[5]);
// 移除
list.Remove("G");
Print("根据元素移除", list);
list.RemoveAt(0);
Print("根据索引移除", list);
// 反转
list.Reverse();
Print("反转", list);
// 清空
list.Clear();
Print("清空", list);
}
// Output:
// 8
// 7
// 遍历:ABCDEFG
// 索引:F
// 根据元素移除:ABCDEF
// 根据索引移除:BCDEF
// 反转:FEDCB
// 清空:
/* 定义一个打印的方法(注:foreach 中的使用的object类型)*/
private static void Print(string log, ArrayList list)
{
Console.Write(log + ":");
foreach (object o in list)
{
Console.Write(o.ToString());
}
Console.WriteLine();
}
private static void Main(string[] args)
{
// 实例化
ArrayList list = new ArrayList();
// 添加
list.Add("A");
list.Add("B");
list.Add("C");
list.Add(1);
list.Add(2);
list.Add(3);
list.Add(true);
// 属性
Console.WriteLine(list.Capacity);
Console.WriteLine(list.Count);
// 遍历
Console.Write("遍历:");
foreach (object o in list)
Console.Write(o);
Console.WriteLine();
// 索引
Console.WriteLine("索引:" + list[5]);
// 移除
list.Remove("True");
Print("根据元素移除", list);
list.RemoveAt(0);
Print("根据索引移除", list);
// 反转
list.Reverse();
Print("反转", list);
// 清空
list.Clear();
Print("清空", list);
}
// Output:
// 8
// 7
// 遍历:ABC123True
// 索引:3
// 根据元素移除:ABC123True
// 根据索引移除:BC123True
// 反转:True321CB
// 清空:
三、键值集合⚓︎
键值集合通过键值对来维护元素,Key 与 Vlaue 成对出现。
.Net类⚓︎
Dictionary<TKey,TValue>
是 .NET System.Collections.Generic 中提供的保存键值元素的集合,它替代了早期 System.Collections 中的 Hashtable
。当你需要使用一组键到一组值的映射时,可以考虑使用该类型。
字典中的每一个添加项都由一个值及其关联键组成。通过键检索值非常快,接近于O(1),因为Dictionary 类被实现为哈希表。
Note
Dictionary<TKey,TValue> 具有以下特点:
元素无序性。存储的元素是无序的,字典根据元素添加的顺序存储元素。
Key 不能相同,但 Value 可以。字典通过相等比较器确保每个键都是唯一的,只要一个对象被用作键,它就不能以任何影响其哈希值的方式改变。
Key 不能为 null,但 Value 可以。如果类型 TValue 是引用类型,那么它可以取 null,但键不可以。
非线程安全的。执行多次读取操作是安全的,但如果在读取时修改集合,则会出现问题。
Tip
尽管 Hashtable 是线程安全的,但仍不建议使用它,有关线程安全的替代,请参阅 ConcurrentDictionary<TKey,TValue> 类或 ImmutableDictionary<TKey,TValue> 类。
为了便于枚举,字典中的每一项都被视为表示值及其键的 KeyValuePair<TKey,TValue> 结构。返回项的顺序未定义。由于Dictionary 是键和值的集合,因此元素类型既不是键的类型也不是值的类型。相反,元素类型是键类型和值类型的 KeyValuePair ,你可以在遍历时用到这个类型。
继承体系⚓︎
public class Dictionary<TKey,TValue> :
System.Collections.Generic
.ICollection<System.Collections.Generic.KeyValuePair<TKey,TValue>>,
.IDictionary<TKey,TValue>,
.IEnumerable<System.Collections.Generic.KeyValuePair<TKey,TValue>>,
.IReadOnlyCollection<System.Collections.Generic.KeyValuePair<TKey,TValue>>,
.IReadOnlyDictionary<TKey,TValue>,
System.Collections.IDictionary, System.Runtime.Serialization
.IDeserializationCallback,
.ISerializable
基本使用⚓︎
四、集⚓︎
集(Set)和列表类似,它也可以存储一组元素,与 List 不同的是,Set 不允许重复元素且无法索引。它与数学概念中的集合一致。
.NET 类⚓︎
HashSet<T>
是 .NET System.Collections.Generic 中提供的保存非重复元素的集合,在 System.Collcetion 中没有类似的类。
HashSet 类基于数学集模型,提供了高性能的集合操作,如交集、并集、差集等。Set 是不包含重复元素的集合,其元素没有特定的顺序。HashSet 类提供了类似于访问 Dictionary 或 Hashtable 集合的键的高性能集操作。简单来说,HashSet 类可以看作是一个没有值的 Dictionary 集合。
Note
HashSet 具有以下特点:
元素无序性。存储的元素是无序的,并且没有不能通过索引器获取元素。
不能存储重复元素。该集合不会将一个已经存在的元素再次存储到集合中。
允许存储 null 。该集合允许存储 null,但只会存储一次。
非线程安全的。
Tip
HashSet 集合没有排序,不能包含重复的元素。对于应用程序,如果顺序或元素复制比性能更重要,请考虑将List 类与 Sort 方法结合使用。
继承体系⚓︎
public class HashSet<T> :
System.Collections.Generic.ICollection<T>,
System.Collections.Generic.IEnumerable<T>,
System.Collections.Generic.IReadOnlyCollection<T>,
System.Collections.Generic.IReadOnlySet<T>,
System.Collections.Generic.ISet<T>,
System.Runtime.Serialization.IDeserializationCallback,
System.Runtime.Serialization.ISerializable
基本使用⚓︎
五、排序列表⚓︎
.NET 类⚓︎
SorteList<T>
是 .NET System.Collections.Generic 命名空间中提供的基于可自定义 IComparer<T> 实现 按键进行排序 的 键/值对 的集合,它替代了 System.Collections 中的 SortedList
类。
SortedList<TKey,TValue>泛型类是一个具有 O(log n) 检索的键/值对数组,其中 n 是列表中元素的数量。它支持通过 Keys
和 Values
属性索引器返回对键和值进行高效的检索。访问属性时不需要重新生成列表,因为列表只是键和值的内部数组的包装器。下面的代码展示了 Values 属性从一个排序的字符串列表中索引检索值的用法:
SortedList 中的每个元素都可以作为 KeyValuePair
SortedList 的容量是 SortedList 可以容纳的元素数量。当元素被添加时,通过重新分配内部数组,容量会根据需要自动增加。容量可以通过调用 TrimExcess
方法或显式设置 Capacity 属性来减小。减小容量会重新分配内存并复制 SortedList 中的所有元素。
Note
SortedList<T>
具有以下特点:
元素有序。基于比较器
Comparer<T>
实现对键的排序;
允许键重复,但不允许值重复。键用于索引值,每个键必须是唯一的;
键不允许为 null ,但值可以。如果列表中的值类型TValue是引用类型,则值可以为空;
非线程安全的。为了保证枚举期间的线程安全,可以在整个枚举期间锁定集合。要允许多个线程访问集合进行读写,必须实现自己的同步。
继承体系⚓︎
public class SortedList<TKey,TValue> :
System.Collections
.IDictionary,
.Generic
.ICollection<System.Collections.Generic.KeyValuePair<TKey,TValue>>,
.IDictionary<TKey,TValue>,
.IEnumerable<System.Collections.Generic.KeyValuePair<TKey,TValue>>,
.IReadOnlyCollection<System.Collections.Generic.KeyValuePair<TKey,TValue>>,
.IReadOnlyDictionary<TKey,TValue>
// 缩进替代省略命名空间
基本使用⚓︎
七、堆栈⚓︎
栈(Stack)是一种具有后进先出(Last In First Out, LIFO)的数据结构。
.NET 类⚓︎
Stack<T>
是 .NET System.Colloections.Generic 中提供的存储相同指定类型实例的可变大小、后进先出集合,它替代了早期在 System.Collections 中提供的 Stack
类。当你需要临时存储信息时,堆栈和队列很有用。也就是说,您可能希望在检索元素值后丢弃元素。
如果需要以相反的顺序访问信息,则使用栈;如果需要按照信息在集合中存储的顺序访问信息,则使用队列。
Stack<T\>
实现为数组,Stack 的容量是可以容纳的元素数量。当元素被添加到 Stack 时,通过重新分配内部数组,容量会根据需要自动增加,您可以通过调用 TrimExcess
方法来减少容量。
Stack<T>
具有三个主要操作:
Push
将一个<T>类型元素插入到堆栈的顶部;Pop
从堆栈顶部移除一个<T>类型的元素;Peek
返回一个位于堆栈顶部的元素,但不会从堆栈中删除它。
Question
当 Stack<T> 容量不足时,通过重新分配内部数组,容量会根据需要自动增减。如果 Count 小于堆栈的容量,则 Push 是一个 O(1) 操作。如果容量需要增加以容纳新元素,则 Push 变成 O(n) 操作,其中 n 为 Count。Pop是一个O(1)操作。
Note
Stack<T> 具有以下特点:
后进先出。最先被添加的元素被存储在栈底,后添加的元素先被取出。
元素可重复。允许添加重复元素。
元素可为 null 。 接受null作为引用类型的有效值。
非线程安全。该类不是线程安全的,当需要从多个线程并发访问集合时,请参阅ConcurrentStack<T> 类型
继承体系⚓︎
public class Stack<T> :
System.Collections.Generic.IEnumerable<T>,
System.Collections.Generic.IReadOnlyCollection<T>,
System.Collections.ICollection
基本使用⚓︎
八、队列⚓︎
队列(Queue)是一种具有先进先出(First In First Out, FIFO)的数据结构。
.NET 类⚓︎
Queue<T>
是 .NET System.Colloections.Generic 中提供的存储相同指定类型实例的可变大小、先进先出集合,它替代了早期在 System.Collections 中提供的 Queue
类。当你需要临时存储信息时,堆栈和队列很有用。也就是说,您可能希望在检索元素值后丢弃元素。
如果需要按照信息在集合中存储的顺序访问信息,则使用队列;如果需要以相反的顺序访问信息,则使用栈;
该类将泛型队列实现为一个循环数组,Queue 的容量是 Queue 可以容纳的元素数量。当元素被添加到Queue 时,通过重新分配内部数组,容量会根据需要自动增加,您可以通过调用 TrimExcess
方法来减少容量。
Note
Queue 通过容量因子来完成扩容,新容量 = 容量因子 * 当前容量,容量因子默认为 2.0;
Queue<T>
具有三个主要操作:
Enqueue
将一个 <T> 类型元素添加到队列的末尾;Dequeue
从Queue 开始删除最老的元素;Peek
返回一个位于队列开头的元素,但不会从队列中删除它。
Note
Queue<T> 具有以下特点:
先进先出。最先被添加的元素被存储在队列开头,先添加的元素先被取出。
元素可重复。允许添加重复元素。
元素可为 null 。 接受null作为引用类型的有效值。
非线程安全。该类不是线程安全的,当需要从多个线程并发访问集合时,请参阅ConcurrentQueue<T> 类
继承体系⚓︎
public class Queue<T> :
System.Collections.Generic.IEnumerable<T>,
System.Collections.Generic.IReadOnlyCollection<T>,
System.Collections.ICollection
基本使用⚓︎
九、点列阵⚓︎
点列阵也称位数组,是用来存储位数据(0或1)集合结构。
.NET 类⚓︎
BitArray
是 .NET System.Collections 命名空间下提供的存储 1 和 0 的紧缩型二进制数组类,它使用布尔值来表示,其中 true 表示位是开启的(1),false 表示位是关闭的(0)。当您需要存储位,但是事先不知道位数时,则使用点阵列。您可以使用整型索引从点阵列集合中访问各项,索引从零开始。
BitArray 的容量(Capacity)总是与计数(Count)相同。元素的增加和减少通过 Length 属性加减完成,BitArray的大小由客户端控制;可以使用整数索引访问此集合中的元素。此集合中的索引是从零开始的,索引超过BitArray的末尾会抛出一个ArgumentException。
BitArray类提供了在其他集合中找不到的方法,包括那些允许使用过滤器一次修改多个元素的方法,如And
、Or
、Xor
、not
和 SetAll
。
System.Collections.Specialized
命名空间下的 BitVector32
类是一个提供与 BitArray
相同功能的结构,但具有更快的性能。BitVector32 更快,因为它是一个值类型,因此内存分配在堆栈上,而BitArray 是一个引用类型,因此内存分配在堆上。BitVector32 可以存储32位,而 BitArray 可以存储可变数量的位。
Tip
BitVector32存储位标志和小整数,因此对于不向用户公开的数据非常理想。然而,如果所需位标志的数量是未知的,是可变的,或者大于32,则使用BitArray代替。
Note
BitArray 具有以下特点:
允许元素重复。允许存储多个位值。
不允许存储 null。点列阵只能存储 true or false。
非线程安全的。为了保证枚举期间的线程安全,您可以在整个枚举期间锁定集合,或者捕获由其他线程所做的更改所导致的异常。