集合
选择集合
一般情况下,应使用泛型集合。 下表介绍了一些常用的集合方案和可用于这些方案的集合类。 如果你是使用泛型集合的新手,此表将帮助你选择最适合你的任务的泛型集合。
| 我要…… | 泛型集合选项 | 非泛型集合选项 | 线程安全或不可变集合选项 |
|---|---|---|---|
| 将项存储为键/值对以通过键进行快速查找 | Dictionary |
Hashtable (根据键的哈希代码组织的键/值对的集合。) |
ConcurrentDictionary ReadOnlyDictionary ImmutableDictionary |
| 按索引访问项 | List |
Array ArrayList |
ImmutableList ImmutableArray |
| 使用项先进先出 (FIFO) | Queue |
Queue | ConcurrentQueue ImmutableQueue |
| 使用数据后进先出 (LIFO) | Stack |
Stack | ConcurrentStack ImmutableStack |
| 按顺序访问项 | LinkedList |
无建议 | 无建议 |
| 删除集合中的项或向集合添加项时接收通知。 (实现 INotifyPropertyChanged 和 INotifyCollectionChanged) | ObservableCollection |
无建议 | 无建议 |
| 已排序的集合 | SortedList |
SortedList | ImmutableSortedDictionary ImmutableSortedSet |
| 数学函数的一个集 | HashSet SortedSet |
无建议 | ImmutableHashSet ImmutableSortedSet |
HashSet
抽象基类 KeyedCollection
ConcurrentStack、ConcurrentQueue和ConcurrentBag类型内部是使用链表实现的。因此,其内存利用不如非并发的Stack和Queue高效。但是它们适用于并发访问,因为链表更容易实现无锁算法或者少锁的算法。
ConcurrentBagnull 作为引用类型的有效值。
一个ConcurrentBag
在调用Take时,ConcurrentBag
因此,准确地说,Take方法将返回调用线程在集合中最近添加的元素。如果该线程上已经没有任何元素,它会返回其他线程(随机挑选)最近添加的元素。
集合的算法复杂性
不可变的集合类型通常性能较低,但却提供了不可变性,这通常是一种非常有效的优点。
| 可变 | 分期 | 最坏情况 | 不可变 | 复杂性 |
|---|---|---|---|---|
Stack |
O(1) | O(n) |
ImmutableStack |
O(1) |
Queue |
O(1) | O(n) |
ImmutableQueue |
O(1) |
List |
O(1) | O(n) |
ImmutableList |
O(log n) |
List |
O(1) | O(1) | ImmutableList |
O(log n) |
List |
O(n) |
O(n) |
ImmutableList |
O(n) |
HashSet, lookup |
O(1) | O(n) |
ImmutableHashSet |
O(log n) |
SortedSet |
O(log n) |
O(n) |
ImmutableSortedSet |
O(log n) |
Dictionary |
O(1) | O(n) |
ImmutableDictionary |
O(log n) |
Dictionary lookup |
O(1) | O(1) - 或者从严格意义上说,O(n) |
ImmutableDictionary lookup |
O(log n) |
SortedDictionary |
O(log n) |
O(n log n) |
ImmutableSortedDictionary |
O(log n) |
由于其索引器的 O(log n) 时间,ImmutableList 在 for 循环内的效果较差。 使用 foreach 循环枚举 ImmutableList 很有效,因为 ImmutableList 使用二进制树来存储其数据,而不是像 List 那样使用简单数组。 数组可以非常快速地编入索引,但必须向下遍历二进制树,直到找到具有所需索引的节点。
此外,SortedSet 与 ImmutableSortedSet 的复杂性相同。 这是因为它们都使用了二进制树。 当然,显著的差异在于 ImmutableSortedSet 使用不可变的二进制树。 由于 ImmutableSortedSet 还提供了一个允许变化的 System.Collections.Immutable.ImmutableSortedSet
检查是否相等
诸如 Contains、 IndexOf、 LastIndexOf和 Remove 的方法将相等比较器用于集合元素。 如果集合是泛型的,则按照以下原则比较项是否相等:
-
如果类型 T 实现 IEquatable
泛型接口,则相等比较器是该接口的 Equals 方法。 -
如果类型 T 未实现 IEquatable
,则使用 Object.Equals 。
此外,字典集合的某些构造函数重载接受 IEqualityComparer
确定排序顺序
对于比较对象,有 default comparer 和 explicit comparer的概念。
默认比较器依赖至少一个正在被比较的对象来实现 IComparable 接口。 在用作列表集合的值或字典集合的键的所有类上实现 IComparable 不失为一个好办法。 对泛型集合而言,等同性比较是根据以下内容确定的:
-
如果类型 T 实现 System.IComparable
泛型接口,则默认比较器是该接口的 IComparable .CompareTo(T) 方法。 -
如果类型 T 实现非泛型 System.IComparable 接口,则默认比较器是该接口的 IComparable.CompareTo(Object) 方法。
-
如果类型 T 未实现任何接口,则没有默认比较器,必须显式提供一个比较器或比较委托。
为了提供显式比较,某些方法接受 IComparer 实现作为参数。 例如, List
SortedList
| SortedList | SortedDictionary |
|---|---|
| 返回键和值的属性是有索引的,从而允许高效的索引检索。 | 无索引的检索。 |
检索属于 O(log n) 操作。 |
检索属于 O(log n) 操作。 |
插入和删除通常属于 O(n) 操作;不过,对于已按排序顺序排列的数据,插入属于 O(log n) 操作,这样每个元素都可以添加到列表的末尾。 (这假设不需要调整大小。) |
插入和删除属于 O(log n) 操作。 |
| 比 SortedDictionary |
比 SortedList 非泛型类和 SortedList |
对于必须可通过多个线程并发访问的已排序列表或字典,可以向派生自 ConcurrentDictionary
并发集合类型使用轻量同步机制,如 SpinLock、SpinWait、SemaphoreSlim 和 CountdownEvent,这些机制是 .NET Framework 4 中的新增功能。 这些同步类型通常在将线程真正置于等待状态之前,会在短时间内使用忙旋转。 预计等待时间非常短时,旋转比等待所消耗的计算资源少得多,因为后者涉及资源消耗量大的内核转换。 对于使用旋转的集合类,这种效率意味着多个线程能够以非常快的速率添加和删除项。
BlockingCollection
创建 BlockingCollection
在使用者需要同时取出多个集合中的项的情况下,可以创建 BlockingCollection
//Generate some source data.
BlockingCollection[] sourceArrays = new BlockingCollection[5];
for(int i = 0; i < sourceArrays.Length; i++)
sourceArrays[i] = new BlockingCollection(500);
Parallel.For(0, sourceArrays.Length * 500, (j) =>
{
int k = BlockingCollection.TryAddToAny(sourceArrays, j);
if(k >=0)
Console.WriteLine("added {0} to source data", j);
});
foreach (var arr in sourceArrays)
arr.CompleteAdding();
ConcurrentDictionary
此外,尽管 ConcurrentDictionary
-
threadA 调用 GetOrAdd,未找到项,通过调用
valueFactory委托创建要添加的新项。 -
threadB 并发调用 GetOrAdd,其
valueFactory委托受到调用,并且它在 threadA 之前到达内部锁,并将其新键值对添加到词典中 。 -
threadA 的用户委托完成,此线程到达锁位置,但现在发现已有项存在。
-
threadA 执行“Get”,返回之前由 threadB 添加的数据 。
因此,无法保证 GetOrAdd 返回的数据与线程的 valueFactory 创建的数据相同。 调用 AddOrUpdate 时可能发生相似的事件序列。
Microsoft.Extensions.ObjectPool 命名空间下已存在 Microsoft.Extensions.ObjectPool.ObjectPool