集合和数据结构

存储并作为集合时,通常可以更有效地处理类似的数据。 可以使用System.Array类或命名空间中的System.CollectionsSystem.Collections.GenericSystem.Collections.Concurrent类添加、删除和System.Collections.Immutable修改集合中的单个元素或元素范围。

有两种主要类型的集合:泛型集合和非泛型集合。 泛型集合在编译时是类型安全的。 因此,泛型集合通常提供更好的性能。 泛型集合在构造时会接受一个类型参数。 在集合中添加或移除项时,它们不要求强制转换为 Object 类型和从此类型强制转换。 此外,Windows 应用商店应用中支持大多数通用集合。 非泛型集合将项存储为Object,因此需要进行强制类型转换,而且大多数集合不支持 Windows 应用商店应用开发。 但是,在旧代码中可能会看到非泛型集合。

在 .NET Framework 4 及更高版本中,命名空间中的 System.Collections.Concurrent 集合提供高效的线程安全作,用于从多个线程访问收集项。 命名空间(System.Collections.Immutable)中的不可变集合类本质上是线程安全的,因为对原始集合的副本执行作,并且无法修改原始集合。

常见集合功能

所有集合都提供用于在集合中添加、删除或查找项的方法。 此外,直接或间接实现 ICollection 接口或 ICollection<T> 接口的所有集合共享以下功能:

此外,许多集合类包含以下功能:

  • 容量和计数属性

    集合的容量是可以包含的元素数。 集合的计数是它实际包含的元素数。 某些集合隐藏容量、计数或将这两者都隐藏。

    达到当前容量时,大多数集合会自动扩展容量。 将重新分配内存,并将元素从旧集合复制到新集合。 此设计减少了使用集合所需的代码。 但是,集合的性能可能会受到负面影响。 例如,对于List<T>,如果Count小于Capacity,则添加项是一个O(1)操作。 如果需要增加容量以适应新元素,则添加项将变为 O(n)操作,其中nCount。 避免多个重新分配导致性能不佳的最佳方式是将初始容量设置为集合的估计大小。

    BitArray 是一种特殊情况,其容量与长度相同,并且与数量一致。

  • 一致的下限

    集合的下限是其第一个元素的索引。 命名空间中的所有 System.Collections 索引集合的下限为零,这意味着它们为 0 索引。 Array默认下限为零,但在使用创建Array.CreateInstance类的实例时,可以定义不同的下限。

  • 同步以从多个线程进行访问(仅 System.Collections 类)。

    命名空间中的 System.Collections 非泛型集合类型通过同步提供一些线程安全性;通常通过 SyncRootIsSynchronized 成员公开。 默认情况下,这些集合不是线程安全的。 如果需要对集合进行可缩放且高效的多线程访问,请使用命名空间中的 System.Collections.Concurrent 一个类或考虑使用不可变集合。 有关详细信息,请参阅 Thread-Safe 集合

选择集合

一般情况下,应使用泛型集合。 下表介绍了一些常见集合方案和可用于这些方案的集合类。 如果你不熟悉泛型集合,下表将帮助你选择最适合任务的泛型集合:

我想。。。 泛型集合选项 非泛型集合选项 线程安全或不可变集合选项
将项存储为键/值对以通过键进行快速查找 Dictionary<TKey,TValue> Hashtable

(根据键的哈希代码组织的键/值对的集合。)
ConcurrentDictionary<TKey,TValue>

ReadOnlyDictionary<TKey,TValue>

ImmutableDictionary<TKey,TValue>
按索引访问项 List<T> Array

ArrayList
ImmutableList<T>

ImmutableArray
使用项先进先出 (FIFO) Queue<T> Queue ConcurrentQueue<T>

ImmutableQueue<T>
使用数据后进先出 (LIFO) Stack<T> Stack ConcurrentStack<T>

ImmutableStack<T>
按顺序访问项 LinkedList<T> 无建议 无建议
当项被删除或添加到集合时接收通知。 (实现 INotifyPropertyChangedINotifyCollectionChanged ObservableCollection<T> 无建议 无建议
已排序的集合 SortedList<TKey,TValue> SortedList ImmutableSortedDictionary<TKey,TValue>

ImmutableSortedSet<T>
数学函数集 HashSet<T>

SortedSet<T>
无建议 ImmutableHashSet<T>

ImmutableSortedSet<T>

集合的算法复杂性

选择 集合类时,需要考虑性能的潜在权衡。 使用下表引用各种可变集合类型在算法复杂性与相应的不可变集合类型之间的比较方式。 通常不可变的集合类型性能较低,但提供不可变性 - 这通常是一个有效的比较优势。

可变 分期 最差情况 不可变的 复杂性
Stack<T>.Push O(1) O(n ImmutableStack<T>.Push O(1)
Queue<T>.Enqueue O(1) O(n ImmutableQueue<T>.Enqueue O(1)
List<T>.Add O(1) O(n ImmutableList<T>.Add O(日志 n
List<T>.Item[Int32] O(1) O(1) ImmutableList<T>.Item[Int32] O(日志 n
List<T>.Enumerator O(n O(n ImmutableList<T>.Enumerator O(n
HashSet<T>.Add,查找 O(1) O(n ImmutableHashSet<T>.Add O(日志 n
SortedSet<T>.Add O(日志 n O(n ImmutableSortedSet<T>.Add O(日志 n
Dictionary<T>.Add O(1) O(n ImmutableDictionary<T>.Add O(日志 n
Dictionary<T> 查找 O(1) O(1)或者严格来说为 O(n ImmutableDictionary<T> 查找 O(日志 n
SortedDictionary<T>.Add O(日志 n O(n 日志 n ImmutableSortedDictionary<T>.Add O(日志 n

使用List<T>循环或for循环可以有效地枚举一个foreach。 但是,由于其索引器的 O(log ImmutableList<T>) 时间,forn 循环内的效果较差。 使用ImmutableList<T>循环枚举foreach是有效的,因为ImmutableList<T>使用二叉树来存储其数据,而不是像List<T>那样使用数组。 数组可以快速编制索引,而二进制树必须向下走,直到找到具有所需索引的节点。

此外,SortedSet<T>ImmutableSortedSet<T> 具有相同的复杂性,因为它们都使用二叉树。 显著区别在于使用 ImmutableSortedSet<T> 不可变二进制树。 由于 ImmutableSortedSet<T> 还提供了一个 System.Collections.Immutable.ImmutableSortedSet<T>.Builder 允许突变的类,因此可以同时具有不可变性和性能。

标题 DESCRIPTION
选择集合类 介绍不同的集合,并帮助你为方案选择一个集合。
常用集合类型 描述常用的泛型和非泛型集合类型,例如 System.ArraySystem.Collections.Generic.List<T>System.Collections.Generic.Dictionary<TKey,TValue>
何时使用泛型集合 讨论泛型集合类型的用法。
集合中的比较和排序 讨论在集合中使用相等比较和排序比较。
已排序的集合类型 描述已排序的集合性能和特征。
哈希表和字典集合类型 介绍泛型和非泛型基于哈希的字典类型的功能。
线程安全集合 描述集合类型,例如 System.Collections.Concurrent.BlockingCollection<T>System.Collections.Concurrent.ConcurrentBag<T>,支持从多个线程进行安全高效的并发访问。
System.Collections.Immutable 介绍不可变集合并提供指向集合类型的链接。

参考文献