这个是对C#中的集合类发展做出简要概述、对比、使用、原理等剖析。
集合是一个数据结构,在不同的业务场景使用的数据结构也是完全不同的,但是历史发展史有进程的,C#中的集合类也是在不断完善的过程,所以从 最小的业务场景来一步步的看看C#中对集合类的实现。
学习数组的时候会对其特点/原理、优点,缺点等几个方面进行学习,对引用场景进行了解。

数组(Array)与集合(ArrayList)与List

数组(Array)

数组可以说是最简单的数据结构了,数组有以下几个特点:

  • 数组存储在连续的内存上。
  • 数组的内容都是相同类型。如果类型为值类型,则将会有size个未装箱的该类型的值被创建。如果类型为引用类型,则将会有size个相应类型的引用被创建。
  • 数组可以直接通过下标访问。

在C#中:

  1. {
  2. int size = 2;
  3. // 声明并实例化数组:数组在实例化得时候必须给出大小。注意这里赋值的是大小,在这里赋值为2,随意数组的下标最大值为[1]。
  4. int[] a = new int[size];
  5. // 数组赋值:值与声明时候的类型必须一致,下标索引从0开始。
  6. a[0] = 1;
  7. // 数组获取:接收的参数类型必须与数组的一致,当没有赋值的默认为0。
  8. int x = a[1];
  9. }

优点:
由于是在连续内存上存储的,所以它的索引速度非常快,访问一个元素的时间是恒定的也就是说与数组的元素数量无关,而且赋值与修改元素也很简单。
缺点:

  • 由于是连续存储,所以在两个元素之间插入新的元素与删除元素就变得不方便。
  • 声明一个新的数组时,必须指定其长度,这就会存在一个潜在的问题,那就是当我们声明的长度过长时,显然会浪费内存,当我们声明长度过短的时候,则面临这溢出的风险。

    集合(ArrayList)

    集合是为了弥补数组的缺点而推出的。位于System.Collections命名空间下。 特点:

    • 不必在声明ArrayList时指定它的长度,这是由于ArrayList对象的长度是按照其中存储的数据来动态增长与缩减的。
    • ArrayList可以存储不同类型的元素。这是由于ArrayList会把它的元素都当做Object来处理。因而,加入不同类型的元素是允许的。
  1. {
  2. ArrayList test3 = new ArrayList();
  3. //新增数据
  4. test3.Add("liu");
  5. test3.Add(25);
  6. //修改数据
  7. test3[1] = 26;
  8. //删除数据
  9. test3.RemoveAt(0);
  10. }


优点:

  • 不必在声明ArrayList时指定它的长度,这是由于ArrayList对象的长度是按照其中存储的数据来动态增长与缩减的。
  • ArrayList可以存储不同类型的元素。这是由于ArrayList会把它的元素都当做Object来处理。因而,加入不同类型的元素是允许的。

缺点:

  • ArrayList不是类型安全的。因为把不同的类型都当做Object来做处理,很有可能会在使用ArrayList时发生类型不匹配的情况。
  • ArrayList由于把所有类型都当做了Object,所以不可避免的当插入值类型时会发生装箱操作,在索引取值时会发生拆箱操作。

    List

    可以说为了解决ArrayList的缺点,而且在C#2.0的时候引入了泛型的概念,所以推出了List这种结合。 特点: 和ArrayList很相似,长度都可以灵活的改变,最大的不同在于在声明List集合时,我们同时需要为其声明List集合内数据的对象类型,这点又和Array很相似,其实List内部使用了Array来实现。

  1. {
  2. // 这里使用IList接口来调用子类实现,为了满足依赖倒置原则
  3. IList<string> test4 = new List<string>();
  4. //新增数据
  5. test4.Add("Fanyoy");
  6. test4.Add("Chenjd");
  7. //修改数据
  8. test4[1] = "murongxiaopifu";
  9. //移除数据
  10. test4.RemoveAt(0);
  11. }


优点:

  • 确保了类型安全。
  • 取消了装箱和拆箱的操作。
  • 它融合了Array可以快速访问的优点以及ArrayList长度可以灵活变化的优点。

缺点:
他是现在最长使用的集合类型,经过多次的改进,已经很完备了,他弥补了Array与ArrayList的缺点,同时拥有他们的有点。

探究原理

自己实现一个MyList来看一下内部原理:
这里主要是扩容的原理,容量不够会增加为原来容量的二倍。

  1. /// <summary>
  2. /// 首先这是一个泛型类,因为自己实现,所以不继承Ilist接口
  3. /// </summary>
  4. /// <typeparam name="T"></typeparam>
  5. class MyList<T>
  6. {
  7. public T this[int index] {
  8. get
  9. {
  10. if ((uint)index >= (uint)Count)
  11. {
  12. throw new Exception("数组越界异常");
  13. }
  14. return Items[index];
  15. }
  16. set
  17. {
  18. if ((uint)index >= (uint)Count)
  19. {
  20. throw new Exception("数组越界异常");
  21. }
  22. Items[index] = value;
  23. }
  24. }
  25. // MyList容量
  26. private int Capacity { get; set; }
  27. // MyList中元素个数
  28. private int Count { get; set; }
  29. // 声明数组
  30. private T[] Items { get; set; }
  31. // 默认容量为4
  32. public MyList(int capacity = 4)
  33. {
  34. if (capacity < 0)
  35. {
  36. throw new Exception("数组越界");
  37. }
  38. else
  39. {
  40. this.Capacity = capacity;
  41. this.Items = new T[capacity];
  42. }
  43. }
  44. //元素添加
  45. public void add(T x)
  46. {
  47. //执行扩容方法
  48. Resize();
  49. this.Items[this.Count] = x;
  50. this.Count++;
  51. }
  52. //根据当前索引移除元素
  53. public void RemoveAt(int index)
  54. {
  55. if ((uint)index >= (uint)Count)
  56. {
  57. throw new Exception("数组越界异常");
  58. }
  59. Count--;
  60. if (index < Count)
  61. {
  62. Array.Copy(Items, index + 1, Items, index, Count - index);
  63. }
  64. Items[Count] = default(T);
  65. }
  66. // 私有的扩容方法
  67. private void Resize()
  68. {
  69. if (this.Capacity <= this.Count)
  70. {
  71. // 容量变为原来得二倍
  72. this.Capacity = this.Capacity * 2;
  73. if (this.Count>this.Capacity)
  74. {
  75. this.Count = this.Capacity;
  76. }
  77. T[] destinationArray = new T[this.Capacity];
  78. Array.Copy(this.Items, destinationArray, this.Count);
  79. this.Items = destinationArray;
  80. }
  81. }
  82. }

单向链表(ListDictionary)与双向链表(LinkedList

单向链表(ListDictionary)

因为Array这种数据类型对查找与修改比较友好,但是对删除与增加还是不太理想的。因为array的扩容原理,所以对内存有浪费,但是链表使用的是指针来连接,所以不会浪费内存。其实ListDictionary不是本质的单向链表,他是用单链表实现的字典。 特点:

  • 对内存利用率高。
  1. {
  2. // 创建
  3. ListDictionary listDictionary = new ListDictionary();
  4. // 添加元素
  5. listDictionary.Add("1", "2");
  6. listDictionary.Add("2", "3");
  7. // 移除元素
  8. listDictionary.Remove("1");
  9. }


优点:对内存空间利用高。
缺点:每次添加数据时都要遍历链表,数据量大时效率较低,数据量较大且插入频繁的情况下,不宜选用。且Key与value都是object,类型是不安全的。
自制单向链表

  1. using System;
  2. using System.Collections.Generic;
  3. using System.Linq;
  4. using System.Text;
  5. using System.Threading.Tasks;
  6. namespace _11_Collections
  7. {
  8. public class ListNode<T>
  9. {
  10. public ListNode(T NewValue)
  11. {
  12. Value = NewValue;
  13. }
  14. //前一个
  15. public ListNode<T> Previous;
  16. //后一个
  17. public ListNode<T> Next;
  18. //值
  19. public T Value;
  20. }
  21. class SinglyLinkedList<T>
  22. {
  23. public SinglyLinkedList()//构造函数
  24. {
  25. //初始化
  26. ListCountValue = 0;
  27. Head = null;
  28. Tail = null;
  29. }
  30. private ListNode<T> Head;//头指针
  31. private ListNode<T> Tail;//尾指针
  32. private ListNode<T> Current;//当前指针
  33. private int ListCountValue;//链表数据的个数
  34. //向尾部添加数据方法
  35. public void Append(T DataValue)
  36. {
  37. ListNode<T> NewNode = new ListNode<T>(DataValue);
  38. if (IsNull())//如果头指针为空
  39. {
  40. Head = NewNode;
  41. Tail = NewNode;
  42. }
  43. else
  44. {
  45. Tail.Next = NewNode;
  46. NewNode.Previous = Tail;
  47. Tail = NewNode;
  48. }
  49. Current = NewNode;
  50. ListCountValue += 1;//链表个数加 1
  51. }
  52. public void Delete()
  53. {
  54. if (!IsNull())//若为空链表
  55. {
  56. if (!IsBof())//若删除头
  57. {
  58. Head = Current.Next;
  59. Current = Head;
  60. ListCountValue -= 1;
  61. return;
  62. }
  63. if (!IsEof())//若删除尾
  64. {
  65. Tail = Current.Previous;
  66. Current = Tail;
  67. ListCountValue -= 1;
  68. return;
  69. }
  70. Current.Previous.Next = Current.Next;//若删除中间数据
  71. Current = Current.Previous;
  72. ListCountValue -= 1;
  73. return;
  74. }
  75. }
  76. //向后移动一个数据的方法
  77. public void MoveNext()
  78. {
  79. if (!IsEof()) Current = Current.Next;
  80. }
  81. //向前移动一个数据的方法
  82. public void MovePrevious()
  83. {
  84. if (!IsBof()) Current = Current.Previous;
  85. }
  86. //移动到第一个数据方法
  87. public void MoveFrist()
  88. {
  89. Current = Head;
  90. }
  91. //移动到最后一个数据方法
  92. public void MoveLast()
  93. {
  94. Current = Tail;
  95. }
  96. //判断是否为空链表方法
  97. public bool IsNull()
  98. {
  99. if (ListCountValue == 0)
  100. return true;
  101. return false;
  102. }
  103. //判断是否为到达尾部方法
  104. public bool IsEof()
  105. {
  106. if (Current == Tail)
  107. return true;
  108. return false;
  109. }
  110. //判断是否为到达头部方法
  111. public bool IsBof()
  112. {
  113. if (Current == Head)
  114. return true;
  115. return false;
  116. }
  117. //取得当前结点的值方法
  118. public T GetCurrentValue()
  119. {
  120. return Current.Value;
  121. }
  122. //取得链表的数据个数方法
  123. public int ListCount
  124. {
  125. get { return ListCountValue; }
  126. }
  127. //清空链表方法
  128. public void Clear()
  129. {
  130. MoveFrist();
  131. while (!IsNull())
  132. {
  133. Delete();//若不为空链表,从尾部删除
  134. }
  135. }
  136. //在当前位置前输入数据方法
  137. public void Insert(T DataValue)
  138. {
  139. ListNode<T> NewNode = new ListNode<T>(DataValue);
  140. if (IsNull())
  141. {
  142. Append(DataValue);//为空表,则添加
  143. return;
  144. }
  145. if (IsBof())
  146. {
  147. //为头部插入
  148. NewNode.Next = Head;
  149. Head.Previous = NewNode;
  150. Head = NewNode;
  151. Current = Head;
  152. ListCountValue += 1;
  153. return;
  154. }
  155. //中间插入
  156. NewNode.Next = Current;
  157. NewNode.Previous = Current.Previous;
  158. Current.Previous.Next = NewNode;
  159. Current = NewNode;
  160. ListCountValue += 1;
  161. }
  162. }
  163. }

双向链表(LinkedList

为了解决单向链表的缺点,用LinkedList来代替。

LinkedList<int> vs = new LinkedList<int>();
vs.AddFirst(1);
vs.AddFirst(2);
vs.AddLast(3);

优点:

  • 向链表中插入或删除节点无需调整结构的容量。因为本身不是连续存储而是靠各对象的指针所决定,所以添加元素和删除元素都要比数组要有优势。
  • 链表适合在需要有序的排序的情境下增加新的元素,这里还拿数组做对比,例如要在数组中间某个位置增加新的元素,则可能需要移动移动很多元素,而对于链表而言可能只是若干元素的指向发生变化而已。
  • 有优点就有缺点,由于其在内存空间中不一定是连续排列,所以访问时候无法利用下标,而是必须从头结点开始,逐次遍历下一个节点直到寻找到目标。所以当需要快速访问对象时,数组无疑更有优势。

    总结与对比

    数组的优点:

  • 随机访问性强(通过下标进行快速定位)

  • 查找速度快

数组的缺点:

  • 插入和删除效率低(插入和删除需要移动数据)
  • 可能浪费内存(因为是连续的,所以每次申请数组之前必须规定数组的大小,如果大小不合理,则可能会浪费内存)
  • 内存空间要求高,必须有足够的连续内存空间。
  • 数组大小固定,不能动态拓展

链表的优点:

  • 插入删除速度快(因为有next指针指向其下一个节点,通过改变指针的指向可以方便的增加删除元素)
  • 内存利用率高,不会浪费内存(可以使用内存中细小的不连续空间(大于node节点的大小),并且在需要空间的时候才创建空间)
  • 大小没有固定,拓展很灵活。

链表的缺点:

  • 不能随机查找,必须从第一个开始遍历,查找效率低

双向链表与单向链表的对比:

  • 删除单链表中的某个结点时,一定要得到待删除结点的前驱,得到该前驱有两种方法,第一种方法是在定位待删除结点的同时一路保存当前结点的前驱。第二种方法是在定位到待删除结点之后,重新从单链表表头开始来定位前驱。尽管通常会采用方法一。但其实这两种方法的效率是一样的,指针的总的移动操作都会有2*i次。而如果用双向链表,则不需要定位前驱结点。因此指针总的移动操作为i次。
  • 查找时也一样,我们可以借用二分法的思路,从head(首节点)向后查找操作和last(尾节点)向前查找操作同步进行,这样双链表的效率可以提高一倍。
  • 从存储结构来看,每个双链表的节点要比单链表的节点多一个指针,而长度为n就需要 n*length(这个指针的length在32位系统中是4字节,在64位系统中是8个字节) 的空间,这在一些追求时间效率不高应用下并不适应,因为它占用空间大于单链表所占用的空间;这时设计者就会采用以时间换空间的做法,这时一种工程总体上的衡量。

    HashSet

    hashset是一种以散列表形式的集合,这个集合类包含不重复项的无序列表。HashSet类提供的方法可以创建合集和交集,泛型类的集合,类型安全。 特点: 因为这个集合基于散列值,插入元素的操作非常快,不需要像List类那样重排集合。 HashSet与Dictionary的实现几乎一模一样,只不过没有key,所以存储的时候是用value的HashCode值进行判断。

{
    // hashset维护的是散列表,所以没有索引的。不能按照索引查找。
    HashSet<int> vs = new HashSet<int>();
    vs.Add(1);
    // 删除只能匹配删除,不能按照索引删除
    vs.Remove(1);
}


优点:这个集合基于散列值,插入元素的操作非常快,不需要像List类那样重排集合。
缺点:查找不便。需要比较查找。
本质上可以理解为一个功能集合,主要是对集合内部进行改性能的运算操作,在存储方面并不常用。

HashTable(哈希表)与Dictionary

HashTable(哈希表)

HashTable是一种根据key查找非常快的键值数据结构,不能有重复key,而且由于其特点,其长度总是一个素数,所以扩容后容量会比2倍大一点点,加载因子为0.72f。 当要大量使用key来查找value的时候,HashTable无疑是最有选择,HashTable与ArrayList一样,是非泛型的,value存进去是object,存取会发生装箱、拆箱,所以出现了Dictionary

{
    // hashtable是非泛型的,所以类型不安全。
    Hashtable hashtable = new Hashtable();
    hashtable.Add("a", "asdf");
    hashtable.Add("b", 1);
}

优点:
可以进行大量的key value操作,速度很快。
缺点:
内部为object类型,所以会大量的拆箱装箱操作。

Dictionary

他是为了解决HashTable的类型安全问题而提出的全新的集合类型。字典是类型安全的,也就是说当创建字典时,必须声明key和item的类型,这是第一条字典与哈希表的区别。 在创建字典时,我们可以传入一个容量值,但实际使用的容量并非该值。而是使用“不小于该值的最小质数来作为它使用的实际容量,最小是3。”(老赵),当有了实际容量之后,并非直接实现索引,而是通过创建额外的2个数组来实现间接的索引,即int[] buckets和Entry[] entries两个数组(即buckets中保存的其实是entries数组的下标),这里就是第二条字典与哈希表的区别,还记得哈希冲突吗?对,第二个区别就是处理哈希冲突的策略是不同的!字典会采用额外的数据结构来处理哈希冲突,这就是刚才提到的数组之一buckets桶了,buckets的长度就是字典的真实长度,因为buckets就是字典每个位置的映射,然后buckets中的每个元素都是一个链表,用来存储相同哈希的元素,然后再分配存储空间。

{
    Dictionary<int, int> keyValuePairs = new Dictionary<int, int>();
    keyValuePairs.Add(1, 2);
}

优点:
解决了HashTable的类型安全问题,并对HashTable进行了优化。
缺点:
当数据量小的时候,尽量不要用Dictionary,因为内部实现的关系,消耗空间很大,当数据量大的时候,可以考虑用空间换时间。

内部原理解析

博客: https://blog.csdn.net/fdyshlk/article/details/76407397
C++中STL的Map跟C#的Dictionary的使用几乎是一样的,Map使用的是红黑树来实现,所以想当然的以为C#的Dictionary也是红黑树,老兄,那可真就大错特错了。
我也是有次没事去看下Dictionary的实现才发现压根就没有树的影子,原来使用散列表的方式来实现。下面我们一起来看下Dictionary的内部实现:

public class Dictionary<TKey,TValue>: IDictionary<TKey,TValue>, IDictionary, IReadOnlyDictionary<TKey, TValue>, ISerializable, IDeserializationCallback  {
private struct Entry {
        public int hashCode;    // Lower 31 bits of hash code, -1 if unused
        public int next;        // Index of next entry, -1 if last
        public TKey key;           // Key of entry
        public TValue value;         // Value of entry
    }

    private int[] buckets;
    private Entry[] entries;
    private int count;
    private int version;
    private int freeList;
    private int freeCount;
    private IEqualityComparer<TKey> comparer;
    private KeyCollection keys;
    private ValueCollection values;
    private Object _syncRoot;

可以看到声明了两个数组,其中buckets记录的为指向entries数组的索引,entries记录的为dictionary的value值部分,count为存储的元素的数量,version为当前的版本号,freeList为空闲的列表,freeCount为空闲的列表数量。
我认为dictionary最重要的就是Insert函数,理解了Insert函数就说明你真正理解了dictionary的实现方式了,Add方法最终调用的也是Insert函数,代码如下:

private void Insert(TKey key, TValue value, bool add) {

            if( key == null ) {
                ThrowHelper.ThrowArgumentNullException(ExceptionArgument.key);
            }
            if (buckets == null) Initialize(0);//第一次初始化
           int hashCode = comparer.GetHashCode(key) & 0x7FFFFFFF;//得到key的hashcode进行运算并得到在buckets的索引值

            int targetBucket = hashCode % buckets.Length;

#if FEATURE_RANDOMIZED_STRING_HASHING
            int collisionCount = 0;
#endif
            for (int i = buckets[targetBucket]; i >= 0; i = entries[i].next) {//判断是否已经存在,如果存在,那么进行值得替换

                if (entries[i].hashCode == hashCode && comparer.Equals(entries[i].key, key)) {
                    if (add) { 
                        ThrowHelper.ThrowArgumentException(ExceptionResource.Argument_AddingDuplicate);
                    }
                    entries[i].value = value;
                    version++;
                    return;
                } 

#if FEATURE_RANDOMIZED_STRING_HASHING
                collisionCount++;
#endif
            }
            int index;
            if (freeCount > 0) {//判断entries是否有释放的空闲空间,如果有,则进行利用

                index = freeList;
                freeList = entries[index].next;
                freeCount--;
            }
            else {
                if (count == entries.Length) //数组空间不够,则重新分配两倍的内存进行存储
                {
                    Resize();
                    targetBucket = hashCode % buckets.Length;
                }
                index = count;
                count++;
            }

            entries[index].hashCode = hashCode;//具体的值存储
            entries[index].next = buckets[targetBucket];
            entries[index].key = key;
            entries[index].value = value;
            buckets[targetBucket] = index;
            version++;

#if FEATURE_RANDOMIZED_STRING_HASHING

#if FEATURE_CORECLR
            // In case we hit the collision threshold we'll need to switch to the comparer which is using randomized string hashing
            // in this case will be EqualityComparer<string>.Default.
            // Note, randomized string hashing is turned on by default on coreclr so EqualityComparer<string>.Default will 
            // be using randomized string hashing

            if (collisionCount > HashHelpers.HashCollisionThreshold && comparer == NonRandomizedStringEqualityComparer.Default) 
            {
                comparer = (IEqualityComparer<TKey>) EqualityComparer<string>.Default;
                Resize(entries.Length, true);
            }
#else
            if(collisionCount > HashHelpers.HashCollisionThreshold && HashHelpers.IsWellKnownEqualityComparer(comparer)) 
            {
                comparer = (IEqualityComparer<TKey>) HashHelpers.GetRandomizedEqualityComparer(comparer);
                Resize(entries.Length, true);
            }
#endif // FEATURE_CORECLR

#endif

        }

这段代码的难点就在于buckets数组和entries数组的映射,如果这理解了,代码自然就清晰了。
03_C#之集合 - 图1
在add第一份数据V1的时候,假如hashcode取余的索引数据为0,则把V1放入entries的第一个数据中,buckets[0]存储的数据为V1在entries的索引,所以buckets[0]为0,假设add的第二份数据V2的hashcode值取模后也为0,那么表示这两份数据的key值发生了碰撞,则判断buckets[0]指向的entries链的key值是否与当前插入V2对应的key值相等,如果相等则用V2替换原有值,如果不等,则V2插入到entries的空闲列表中(因为remove,中间空出的内存块),如果不存在空闲列表则直接插入到末尾,本例因为刚添加,所以直接添加到entries[1]中,然后把entries[1]中的hashcode值赋为V2对应key的hashcode值,next指向0,对应buckets[0]也改成指向entries[1],插入V3与插入V2同理。多添加几个value值时,有可能如下图所示:
03_C#之集合 - 图2
还有个点时如同List一样,Dictionary在数组不够用的情况下会分配2倍的新内存来进行存储,与List不同的是capacity的默认值为3,而且dictionary包含了buckets和entries两个数组,entries是通过Array.Copy直接拷贝到新地址,但是buckets的计算方式是haahcode对长度取余,所以在长度变化后,不能直接通过内存拷贝的方式,而是必须全部再重新计算一遍。在Dictionary的构造函数中可以直接对其进行设置,所以capacity的值如果能设置的合理,那么可以减少内存申请、拷贝的次数,效率也会提高很多。

Queue、Queue

这两个本质上就是一个数据结构,区别就是泛型的区别,类型安全的区别,所以只了解一个就可以了。 在Queue这种数据结构中,最先插入在元素将是最先被删除;反之最后插入的元素将最后被删除,因此队列又称为“先进先出”(FIFO—first in first out)的线性表。通过使用Enqueue和Dequeue这两个方法来实现对 Queue 的存取。 特点:

  • 先进先出的情景。
  • 默认情况下,Queue的初始容量为32, 增长因子为2.0。
  • 当使用Enqueue时,会判断队列的长度是否足够,若不足,则依据增长因子来增加容量,例如当为初始的2.0时,则队列容量增长2倍。
{
    Queue queue = new Queue();
    queue.Enqueue("asd");
}

Stack、Stack

与Queue相对,当需要使用后进先出顺序(LIFO)的数据结构时,我们就需要用到Stack了。 特点:

  • 后进先出的情景。
  • 默认容量为10。
  • 使用pop和push来操作。
{
    Stack stack = new Stack();
    stack.Push("asdfa");
}

SortedList、SortedList与SortedDictionary

SortedList

SortedList集合中的数据是有序的。可以通过key来匹配数据,也可以通过int下标来获取数据。 特点: 添加操作比ArrayList,Hashtable略慢;查找、删除操作比ArrayList快,比Hashtable慢。 所以用Hashtable更好。

{
    SortedList sortedList = new SortedList();
    sortedList.Add("a", "1");
}

SortedDictionary

SortedDictionary相比于SortedList其性能优化了,SortedList其内部维护的是数组而SortedDictionary内部维护的是红黑树(平衡二叉树)的一种,因此其占用的内存,性能都好于SortedDictionary。唯一差在不能用下标取值。

{
    SortedDictionary<int, int> keyValuePairs = new SortedDictionary<int, int>();
    keyValuePairs.Add(1, 2);
}

HybridDictionary

HybridDictionary的类,充分利用了Hashtable查询效率高和ListDictionary占用内存空间少的优点,内置了Hashtable和ListDictionary两个容器,添加数据时内部逻辑如下: 当数据量小于8时,Hashtable为null,用ListDictionary保存数据。 当数据量大于8时,实例化Hashtable,数据转移到Hashtable中,然后将ListDictionary置为null。

{
    HybridDictionary hybridDictionary = new HybridDictionary();
    hybridDictionary.Add("1", "a");
}

BitArray

BitArray这个东东是用于二进制运算,”或”、”非”、”与”、”异或非”等这种操作,只能存true或false;