集合类概述
集合类具有以下特点:
-
集合类定义为 System.Collections 或 System.Collections.Generic 命名空间的一部分。
-
大多数集合类都派生自 ICollection、IComparer、IEnumerable、IList、IDictionary 和 IDictionaryEnumerator 接口以及它们的等效泛型接口。
-
使用泛型集合类可以提供更高的类型安全性,在某些情况下还可以提供更好的性能,尤其是在存储值类型时,这些优势会体现得更明显。有关更多信息,请参见泛型的优点。
集合是一组组合在一起的类似的类型化对象。
任何类型的对象都可被组合到 Object 类型的单个集合中,以利用采用该语言继承的构造。例如,C# foreach 语句(Visual Basic 中的 for each)需要集合中的所有对象都属于单一类型。
但是,在 Object 类型的集合中,单独对各元素执行附加的处理,例如装箱和取消装箱或转换,这影响该集合的性能。装箱和取消装箱通常在存储或检索 Object 类型集合中的值类型时发生。
泛型集合(如 List)和强类型非泛型集合(如 StringCollection)可避免造成上述性能损害,前提是元素的类型是集合所需的类型(例如,从 StringCollection 存储或检索字符串)。此外,强类型集合自动执行添加到该集合的每一元素的类型验证。
除添加、移除或搜索元素的方法外,直接或间接实现 ICollection 接口或 ICollection 泛型接口的所有集合还共享若干功能:
-
枚举数。
枚举数是循环访问其关联集合的对象。它可被视作指向集合中任何元素的可移动的指针。一个枚举数只能与一个集合关联,但一个集合可以具有多个枚举数。C# foreach 语句(Visual Basic 中的 for each)使用枚举数并隐藏操作该枚举数的复杂性。
-
同步成员。
同步在访问集合的元素时提供线程安全。默认情况下集合不是线程安全的。在 System.Collections 命名空间中只有几个类提供 Synchronize 方法,该方法能够超越集合创建线程安全包装。但是,System.Collections 命名空间中的所有类都提供 SyncRoot 属性,可供派生类创建自己的线程安全包装。还提供了 IsSynchronized 属性以确定集合是否是线程安全的。ICollection 泛型接口中不提供同步功能。
-
The CopyTo 方法。
使用 CopyTo 方法可将所有集合复制到数组中;但是,新数组中元素的顺序基于枚举数返回它们的顺序。结果数组始终是具有零下限的一维数组。
注意,ICollection 泛型接口包含非泛型接口未包含的成员。
以下功能在 System.Collections 命名空间中的一些类中实现:
-
容量和计数。
集合的容量是其可以包含的元素的数目。集合的计数是其实际包含的元素的数目。BitArray 是一个特例;其容量与其长度相同,而其长度与其计数相同。某些集合隐藏容量或计数(或两者一起隐藏)。
System.Collections 命名空间中的所有集合在达到当前容量时可自动扩充容量。内存被重新分配,元素从旧集合复制到新集合中。这减少了使用集合所需的代码;但是,集合的性能可能仍受到消极影响。避免因多次重新分配导致不佳性能的最佳方法是将初始容量设置为集合的估计的大小。
-
下限。
集合的下限是其第一个元素的索引。System.Collections 命名空间中的所有索引集合的下限均为零。Array 的下限在默认情况下为零,但使用 CreateInstance 创建 Array 类的实例时可以定义不同的下限。
System.Collections 类通常可以分为三种类型:
-
常用集合。
这些集合是数据集合的常见变体,如哈希表、队列、堆栈、字典和列表。常用集合有泛型和非泛型之分。
-
位集合。
这些集合中的元素均为位标志。它们的行为与其他集合稍有不同。
-
专用集合。
这些集合都具有专门的用途,通常用于处理特定的元素类型,如 StringDictionary。
选择集合类时务必要小心。因为每一集合都有其自身的功能,因此每一集合也就具有其自身的限制。集合的专用性越强,其限制也就越多。有关选择集合的提示,请参见选择集合类。
集合类型是数据集合的常见变体,例如哈希表、队列、堆栈、字典和列表。
集合基于 ICollection 接口、IList 接口、IDictionary 接口,或其泛型集合中的相应接口。IList 接口和 IDictionary 接口都是从 ICollection 接口派生的;因此,所有集合都直接或间接基于 ICollection 接口。在基于 IList 接口的集合中(如 Array、ArrayList 或 List)或直接基于 ICollection 接口的集合中(如 Queue、Stack 或 LinkedList),每个元素都只包含一个值。在基于 IDictionary 接口的集合中(如 Hashtable 和 SortedList 类,或者 Dictionary 和 SortedList 泛型类),每个元素都包含一个键和一个值。KeyedCollection 类较为特别,因为它是带有嵌入键的值列表,因此它的行为既像列表又像字典。
泛型集合是设置强类型的最佳方式。不过,如果所用语言不支持泛型,System.Collections 命名空间中包含基集合,如 CollectionBase、ReadOnlyCollectionBase 和 DictionaryBase,这些都是抽象基类,可以扩展这些抽象基类以创建强类型的集合类。
根据存储元素的方式、对元素进行排序的方式、执行搜索的方式以及进行比较的方式的不同,集合可能也有所不同。Queue 类和 Queue 泛型类提供先进先出列表,而 Stack 类和 Stack 类提供后进先出列表。SortedList 类和 SortedList 泛型类提供 Hashtable 类和 Dictionary 泛型类的排序的版本。Hashtable 或 Dictionary 中的元素只能通过元素的键访问;而 SortedList 或 KeyedCollection 中的元素既可以通过元素的键访问,也可以通过元素的索引访问。所有集合中的索引都从零开始,不过 Array 除外,它允许使用索引不从零开始的数组。
Array 类不是 System.Collections 命名空间的一部分。但是,该类仍是一个集合,因为它基于 IList 接口。
Array 对象的秩是 Array 中的维数。一个 Array 可以具有一个或多个秩。
Array 的下限是其第一个元素的索引。一个 Array 可以具有任何下限。默认情况下它的下限为零,但在使用 CreateInstance 创建 Array 类的实例时可以定义不同的下限。
与 System.Collections 命名空间中的类不同,Array 具有固定的容量。若要增加容量,您必须创建具有所需容量的新 Array 对象,将旧 Array 对象中的元素复制到新对象中,然后删除该旧 Array。
但是,只有系统和编译器可以从 Array 类显式派生。用户应使用其使用的语言所提供的数组构造。
ArrayList 或 List 对象是较为复杂的数组。ArrayList 类和 List 泛型类提供多数 System.Collections 类都提供的但 Array 类未提供的一些功能。例如:
-
Array 的容量是固定的,而 ArrayList 或 List 的容量可根据需要自动扩充。如果更改了 Capacity 属性的值,则可以自动进行内存重新分配和元素复制。
-
ArrayList 和 List 提供添加、插入或移除某一范围元素的方法。在 Array 中,您只能一次获取或设置一个元素的值。
-
使用 Synchronized 方法很容易创建 ArrayList 或 List 的同步版本。Array 将实现同步的任务留给了用户。
-
ArrayList 和 List 提供将只读和固定大小包装返回到集合的方法;而 Array 不提供。
另一方面,Array 提供了 ArrayList 和 List 所缺少的某些灵活性。例如:
-
可以设置 Array 的下限,但 ArrayList 或 List 的下限始终为零。
-
Array 可以具有多个维度,而 ArrayList 或 List 始终只是一维的。
-
特定类型(不包括 Object)的 Array 的性能优于 ArrayList,这是因为 ArrayList 的元素属于 Object 类型,所以在存储或检索值类型时通常发生装箱和取消装箱操作。不过,在不需要重新分配时(即最初的容量十分接近列表的最大容量),List 的性能与同类型的数组十分相近。
需要数组的大多数情况都可以改为使用 ArrayList 或 List;它们更容易使用,并且一般与相同类型的数组具有相近的性能。
Array 位于 System 命名空间中;ArrayList 位于 System.Collections 命名空间中;List 位于 System.Collections.Generic 命名空间中。
Hashtable 类和 Dictionary 泛型类实现 IDictionary 接口。Dictionary 泛型类还实现 IDictionary 泛型接口。因此,这些集合中的每个元素都是一个键/值对。
Hashtable 对象由包含集合元素的存储桶组成。存储桶是 Hashtable 中各元素的虚拟子组,与大多数集合中进行的搜索和检索相比,存储桶可令搜索和检索更为便捷。每一存储桶都与一个哈希代码关联,该哈希代码是使用哈希函数生成的并基于该元素的键。
哈希函数是基于键返回数值哈希代码的算法。键是正被存储的对象的某一属性的值。哈希函数必须始终为相同的键返回相同的哈希代码。一个哈希函数能够为两个不同的键生成相同的哈希代码,但从哈希表检索元素时,为每一唯一键生成唯一哈希代码的哈希函数将令性能更佳。
在 Hashtable 中用作元素的每一对象必须能够使用 GetHashCode 方法的实现为其自身生成哈希代码。但是,还可以通过使用接受 IHashCodeProvider 实现作为参数之一的 Hashtable 构造函数,为 Hashtable 中的所有元素指定一个哈希函数。
在将一个对象添加到 Hashtable 时,它被存储在存储桶中,该存储桶与匹配该对象的哈希代码的哈希代码关联。在 Hashtable 内搜索一个值时,将为该值生成哈希代码,并且搜索与该哈希代码关联的存储桶。
例如,一个字符串的哈希函数可以采用该字符串中每一字符的 ASCII 代码并它们添加到一起来生成一个哈希代码。字符串“picnic”将具有与字符串“basket”的哈希代码不同的哈希代码;因此,字符串“picnic”和“basket”将处于不同的存储桶中。与之相比,“stressed”和“desserts”将具有相同的哈希代码并将处于相同的存储桶中。
Dictionary 类与 Hashtable 类的功能相同。对于值类型,特定类型(不包括 Object)的 Dictionary 的性能优于 Hashtable,这是因为 Hashtable 的元素属于 Object 类型,所以在存储或检索值类型时通常发生装箱和取消装箱操作。
System.Collections.SortedList 类、System.Collections.Generic.SortedList 泛型类和 System.Collections.Generic.SortedDictionary 泛型类类似于 Hashtable 类和 Dictionary 泛型类,因为它们也实现 IDictionary 接口,但是它们以基于键的排序顺序维护元素,没有哈希表的 O(1) 插入和检索特性。这三个类具有若干共性:
-
三个类都实现 System.Collections.IDictionary 接口。两个泛型类还实现 System.Collections.Generic.IDictionary 泛型接口。
-
每个元素是一个键/值对,以进行枚举。
注意 非泛型 SortedList 类在被枚举时返回 DictionaryEntry 对象,而两个泛型类则返回 KeyValuePair 对象。
-
元素按照 System.Collections.IComparer 实现(对于非泛型 SortedList)或 System.Collections.Generic.IComparer 实现(对于两个泛型类)排序。
-
每个类提供了返回仅包含键或仅包含值的集合的属性。
下表列举两个排序的列表类与 SortedDictionary 类之间的一些区别。
SortedList 非泛型类和 SortedList 泛型类 | SortedDictionary 泛型类 |
---|---|
返回键和值的属性是有索引的,从而允许高效的索引检索。 |
无索引的检索。 |
检索的运算复杂度为 O(log n)。 |
检索的运算复杂度为 O(log n)。 |
插入和移除的运算复杂度一般为 O(n);但是,对于已经处于排序顺序的数据,插入操作的运算复杂度为 O(1),因此每个元素都被添加到列表的末尾。(这假设不需要调整大小。) |
插入和移除的运算复杂度为 O(log n)。 |
比 SortedDictionary 使用更少的内存。 |
比 SortedList 非泛型类和 SortedList 泛型类使用更多内存。 |
注意 |
---|
对于包含自己的键的值(例如,包含雇员 ID 编号的雇员记录),可以通过从 KeyedCollection 泛型类派生创建带键的集合,该集合具有列表的一些特征和字典的一些特征。 |
Stack 类和 Stack 泛型类都是后进先出集合类,可以实现 ICollection 接口。Stack 泛型类还实现 ICollection 泛型接口。
当需要临时存储信息时(也就是说,可能想在检索了元素的值后放弃该元素),堆栈和队列都很有用。如果需要按照信息存储在集合中的顺序来访问这些信息,请使用 Queue。如果需要以相反的顺序访问这些信息,请使用 Stack。
Stack 常用于在调用其他过程期间保留变量状态。
可以对 Stack 及其元素执行三种主要操作:
-
Push 可在 Stack 的顶部插入一个元素。
-
Pop 可在 Stack 的顶部移除一个元素。
-
Peek 可返回处于 Stack 顶部的元素,但不将其从 Stack 上移除。
队列集合类型
Queue 类和 Queue 泛型类都是先进先出集合类,它们实现 ICollection 接口和 ICollection 泛型接口。
一定要谨慎选择 System.Collections 类。选用错误的类型可能限制您使用集合。
考虑以下问题:
-
您是否需要一个序列列表,其中的元素通常在检索其值后被放弃?
-
是否需要以某种顺序访问元素,例如 FIFO、LIFO 或随机访问?
-
Queue 类和 Queue 泛型类提供 FIFO 访问。
-
Stack 类和 Stack 泛型类提供 LIFO 访问。
-
LinkedList 泛型类允许从开头到末尾或从末尾到开头按顺序访问。
-
其余的集合提供随机访问。
-
-
是否需要通过索引访问每一元素?
-
ArrayList 和 StringCollection 类以及 List 泛型类通过元素的从零开始的索引提供对元素的访问。
-
Hashtable、SortedList、ListDictionary 和 StringDictionary 类以及 Dictionary 和 SortedDictionary 泛型类通过元素的键提供对元素的访问。
-
NameObjectCollectionBase 和 NameValueCollection 类以及 KeyedCollection 和 SortedList 泛型类通过其元素的从零开始的索引或者通过其元素的键提供对元素的访问。
-
-
每一元素将包含一个值、一个键和一个值的组合还是一个键和多个值的组合?
-
一个键和一个值:使用任何基于 IDictionary 接口或 IDictionary 泛型接口的集合。
-
带有嵌入的键的一个值:使用 KeyedCollection 泛型类。
-
一个键和多个值:使用 NameValueCollection 类。
-
是否需要用与输入元素方式不同的方式对元素排序?
-
是否需要信息的快速搜索和检索?
-
对于小集合(10 项或更少),ListDictionary 比 Hashtable 快。SortedDictionary 泛型类提供比 Dictionary 泛型类更快的查找。
-
-
是否需要只接受字符串的集合?
-
StringCollection(基于 IList)和 StringDictionary(基于 IDictionary)都位于 System.Collections.Specialized 命名空间中。
-
此外,通过为泛型类型参数指定 String 类,可以使用 System.Collections.Generic 命名空间中的任何泛型集合类作为强类型字符串集合。
-