.NET中的数据结构——表
.NET 是一个很好的开发平台,它提供了大量的非常有用的类库,大大方便了开发人员的效率。其中的集合类就是开发过程当中必不可少的一部分,虽然用了挺长一段时间了,但是都没有认真的研究过它们的实现方式,以及它们分别属于哪种数据结构。只有了解了上述的问题,我们才能更好的在日常的开发当中更有效的使用它们,所以最近使用 ILSpy 反编译软件好好学习了一下.NET 中的集合类,现在将自己学习的心得总结一下,希望对大家有帮助,有不正确的地方也请大家帮助纠正。
表的数组实现
各类型一维数组(int[]、long[]等)
概述
它们实现了一个长度固定的表结构,它的元素必须储存在一个连续的内存空间中。
功能说明
它提供了表的创建、清除、复制和元素的存取、排序和搜索功能,因为它的长度是固定,所以它不能进行元素的新增和删除。上述功能中的很大一部分功能都是由 Array 类的静态方法提供的,只有少量功能是由数组的实例方法提供的。
表的创建:初始化一个一维数组。
表的清除:由 Array类的静态方法实现,将表中的指定范围内的元素清空,重置为初始值。
表的复制:由 Array类的静态方法实现的浅度复制,将源表中指定范围内的元素复制到目标表中指定的位置;
由实例方法实现的浅复制,其内部还是通过静态方法来实现数组的复制功能。
元素的搜索:由 Array类的静态方法实现。可以正向搜索,也可以反向搜索;可以搜索指定元素在数组中的位置,也可以搜索指定的元素;可以顺序搜索,也可以用二分法搜索(使用二分法搜索时表中的元素必须以排序)。
元素的排序:由Array类的静态方法实现,使用快速排序法将数组元素进行重新排列,以便能够使用二分法进行搜索。
元素的存取:由实例方法实现。使用元素在表中的位置通过下标访问符或 GetValue、SetValue 方法对表元素进行随机存取。
性能说明
元素的存取:我们只能使用元素在表中的位置通过下标访问符或 GetValue、SetValue方法来对元素进行随机存取,所以该操作的时间复杂度为O(1)。但使用GetValue 和 SetValue 方法来进行随机存取时,有可能会出现装箱的额外操作,因为它们都只操作 object 类型的数据,所以尽量使用下标访问符来进行随机存取,以提高性能(对于引用类型没有差别)。
元素的排序:使用快速排序法进行排序,该操作的时间复杂度为 O(N * log N)。
元素的搜索:元素的搜索分为两种情况:顺序搜索和二分搜索。如果使用顺序搜索则该操作的时间复杂度为 O(N);如果使用二分搜索则该操作的时间复杂度为 O(log N)。
适用场景
当我们要操作的数据满足下面条件时就比较适合使用数组:
1、 元素数量固定,不需要动态的添加和删除元素;
2、 能够通过元素在表中的位置进行随机存取;
ArrayList(Namespace: System.Collection;Assembly:mscorlib)
概述
它实现了一个可以动态添加、删除元素的表结构。实际上,它是通过内部维护一个object 类型的数组 _items 来存放所有的元素,所以它也只提供通过元素的位置存取元素的方式。
功能说明
它提供了表的创建、清除、复制,表元素的存取、搜索、新增、删除和排序功能。
表的创建:实例化 _items,并可以将传入的元素集合拷贝(浅度拷贝)到 _items 中。
表的清除(Clear):清除 _items 中的数据,通过调用 Array.Clear 方法来实现的。
表的复制(CopyTo):将表中指定范围内的元素复制到目标表中指定的位置,通过调用 Array.Copy 方法来实现的。
元素的存取(this[]):只能使用元素在表中的位置通过索引器来对元素进行随机存取。
元素的新增:可以将一个新元素添加到表的最后(Add),也可以将其插入到指定的位置(Insert);可以将一组新元素添加到表的最后(AddRange),也可以将其插入到指定的位置(InsertRange)。
元素的删除:可以从表中删除指定的元素(Remove),也可以将表中指定位置的元素删除(RemoveAt),还可以将表中指定范围内的元素删除(RemoveRange)。
元素的排序(Sort):将表中的元素进行快速排序,以便能够使用二分法进行元素的搜索。
元素的搜索:可以进行顺序搜索(IndexOf 和 LastIndexOf,通过调用Array.IndexOf和Array.LastIndexOf方法实现);也可以使用二分法进行搜索(BinarySearch,调用Array.BinarySearch 方法实现)。
性能说明
元素的存取:虽然对于外部来说是使用索引器来对元素进行随机存取,其实其内部就是使用数组的下标对_items 的元素进行存取,所以该操作的时间复杂度为O(1)。
元素的新增:虽然对于外部来说它提供了元素的新增功能,但是对于其内部来说只是指定数组元素的赋值。
如果在表的尾部添加元素(Add),则只是简单的为指定的数组元素赋值,所以该操作的时间复杂度为O(1);如果在表的中间插入元素(Insert),则需要将数组中指定位置之后的元素都向后移动,然后为指定位置的数组元素赋以新值,则该操作的时间复杂度为O(N)。
只要存在元素的新增,这里就会存在一个扩容的问题。ArrayList内部是通过创建容量更大的新的数组,将所有已有元素拷贝到新数组中指定的位置,然后再将新增的元素存入数组。
元素的删除:当表中指定的元素被删除时,需要将 _items 中被删除元素之后的元素都向前移动,即 _items 中的所有元素必须集中在数组的前部,中间不能出现空元素,所以该操作的时间复杂度为 O(N)。
元素的排序:使用快速排序法对表中的元素进行重新排序,该操作的时间复杂度为 O(N * log N)。
元素的搜索:如果使用顺序搜索则该操作的时间复杂度 O(N);如果表元素已经进行过排序,使用二分法搜索则该操作的时间复杂度为 O(log N)。
适用场景
当我们要操作的数据满足下面条件时就比较适合使用ArrayList:
1、 能够通过元素在表中的位置进行随机存取;
2、 能够动态新增和删除元素;
3、 不能保证各个元素的数据类型能够兼容;
4、 能够对元素进行排序。
List<T>(Namespace:System.Collections.Generic;Assembly:mscorlib.dll)
概述
它实现了一个可以动态添加、删除元素的强类型表结构,内部维护一个强类型T的数组 _items 来存放所有的元素,所以它只提供通过元素的位置来存取元素的方式。
它所提供的功能和内部实现与ArrayList 一样,唯一的区别是:ArrayList 相对来说是弱类型的,没有类型安全保障;而List<T> 相对来说是强类型的,有类型安全保障。
ArrayList:它操作的是一个 object 数组,所以加入它的数据都必须先转换为object 类型,这对于值类型来说就需要进行装箱操作,这也意味着ArrayList中可能存放着各种类型的数据,而且这些数据不一定兼容。
List<T>:它操作的是一个指定类型T的数组,这不仅避免了值类型的装箱操作,也提供了类型安全,所有加入它的数据都必须与T类型兼容。
功能说明
参照 ArrayList 的功能说明。
性能说明
参照ArrayList 的性能说明。
适用场景
List<T> 和 ArrayList 的适用场景也只有一个区别——能不能保证各个元素数据类型的兼容性:能够保证就使用List<T>,不能保证就使用ArrayList。
表的链表实现
LinkedList<T>(Namespace:System.Collections.Generic;Assembly:System.dll)
概述
LinkedList<T> 提供了一个以双向链表方式实现的表结构,内部维护了一组 LinkedListNode<T> 类型的节点,每个节点都保存了自己所属的 LinkedList<T>(list)、前一个节点(prev)、后一个节点(next)和储存在该节点的元素(item)。
在链表形式的表中存在节点和元素两个概念,它们虽有关联但不尽相同。元素是表结构需要存储的真正数据,但是它不包含任何能够访问其它元素的信息,这样不可能实现链表形式的表;而节点不仅存储了表结构需要储存的真正数据,还储存了访问其它节点的信息。由上可以看出:元素是表结构需要存储的真正数据;而节点是这些元素的载体,是组成链表的基本单位。
由于链表的组成单位是节点,所以对表元素的存取都必须通过储存元素的节点间接存取,不能直接存取。又因为每个节点都记录了前一个节点和后一个节点的内存地址,所以这些节点不需要存放在连续的内存空间中。
功能说明
它提供了表的创建、清除、复制,表元素的存取、新增、删除和搜索。
表的创建:表的实例化,并可以根据传入的元素创建相应的节点,加入新建的表中。
表的清除:清除表中所有的节点。
表的复制:将表中所有节点中储存的元素复制(浅度复制)到指定数组的指定位置。
元素的存取:必须要先通过元素的搜索获取储存该元素的节点,然后再通过该节点对元素进行存取。
但是对于链表的第一个元素或最后一个元素的存取不用那么繁琐,只需要通过First 或 Last 这两个属性直接获取储存它们的第一个节点或最后一个节点。
元素的新增:新增一个元素其实就是新增一个节点,所以新增元素时可以传入要新增的元素,也可以传入包含该新增元素的节点,但是同一个节点不能加入到不同的链表中。
可以将元素添加到表头(AddFirst),也可以将元素添加到表尾(AddLast),还可以将元素插入到指定元素之前(AddBefore)或之后(AddAfter)。
元素的删除:删除一个元素其实就是删除一个节点,所以删除元素时可以传入要删除的元素,也可以传入要删除的节点(这个节点必须属于该表)。当传入的是要删除的元素时,会先根据该元素搜索到储存该元素的节点,然后将该节点删除。
除了可以删除指定的元素外,还可以删除特殊的元素:第一个元素(RemoveFirst)或最后一个元素(RemoveLast)。
元素的搜索:根据指定的元素从表中搜索储存该元素的节点。可以正向搜索(Find),也可以逆向搜索(FindLast)。
性能说明
元素的搜索:链表中的节点只能逐一的顺序访问,所以该操作的时间复杂度为 O(N)。
元素的存取:链表的组成结构决定了它不能支持随机存取(即:不能通过元素位置来对元素进行直接存取),所以 LinkedList<T> 并不提供随机存取的方式。
因为可以通过 First 或 Last 属性直接获取链表的第一个节点或最后一个节点,所以链表中第一个元素或最后一个元素的存取操作的时间复杂度为 O(1)。
其它的元素都必须先搜索到储存该元素的节点,然后再通过搜索到的节点对元素进行存取,所以该操作的时间复杂度为O(N)。
元素的新增:链表中新增元素的操作比较简单,只需要修改前一节点的next、后一节点的prev 和储存该元素的节点的 prev与next的值。该操作的时间复杂度为 O(1)。
元素的删除:链表中删除元素的操作比较简单,只需要修改前一个节点的next、后一个节点的prev并将删除节点的next、prev和list都重置。该操作的时间复杂度为 O(1)。
适用场景
当我们要操作的数据满足下面条件时就比较适合使用LinkedList<T>:
1、 会频繁的进行元素的新增和删除;
2、 通常只会顺序的访问表中的所有元素,很少需要对元素进行随机存取;
3、 不需要对表中的元素进行排序。