快速入门系列--CLR--03泛型集合

.NET中的泛型集合

在这里主要介绍常见的泛型集合,很多时候其并发时的线程安全性常常令我们担忧。因而简述下.NET并发时线程安全特性,其详情请见MSDN。

  • 普通集合都不支持多重并发写操作
  • 部分支持单线程写和并发读操作
  • 同时.NET4添加了大量并发集合

首先介绍常见的泛型集合接口,其大部分都位于System.Collection.Generic命名空间。

  • IEnumerable<T>,其可以获取一个IEnumerator<T>迭代器,如果从数据库的角度来看,前者是表,后者是游标,同时这两个接口是唯一具有可变性的集合接口。
  • ICollection<T>,它扩展了IEnumerable<T>,添加了Count和IsReadOnly属性,Add和Remove等操作方法,Contains等判定函数,所有的标准泛型集合都实现了该接口。
  • IList<T>,提供定位功能,包括一个索引器、Insert和RemoveAt,我们通常认为可以通过索引对该泛型集合进行随机访问。、
  • IDictionary<TKey, TValue>,表示键值对集合,扩展了ICollection<KeyValuePair<TKey, TValue>>,取值可以用TryXXX方式。
  • ISet<T>表示唯一值集,包含大量集合操作:交、并、补。

接下来介绍具体的集合泛型集合类型,在实际中需要根据具体场景选择最适合的集合类型。

  • List<T>,其是列表的默认选择,内含一个数组,并且提供列表的逻辑大小Count和后台数组的大小Capacity,当数组满了时,会进行扩容。由于是连续型的数据结构,其添加删除操作的成本较高,提供二分查找,查找效率高。同时,其Sort操作会修改原始列表的内容,与OrderBy不同,并且Sort是不稳定的,会出现相等元素顺序不同的情况。
  • 数组,最基础的集合,均派生自System.Array,包括一维数组T[10],二维数组T[10, 20]等,通过Array类的静态方法进行ConvertAll、FindAll和BinarySearch等操作。
  • Colletion<T>,位于System.Colletion.ObjectModel命名空间,为BindingList<T>和ObservableCollection<T>等扩展类型提供基类。与双向绑定相关的集合类型,注意它们只会在包装器发生变化发出通知,而基础列表改变时不会引发任何事件。
  • ReadOnlyCollection<T>和ReadOnlyObservableCollection<T>,其也类似于包装器,后者实现了INotifyCollectionChanged, INotifyPropertyChanged两个接口。
  • Dictionary<TKey, TValue>,使用散列表,查找性能的优劣取决于散列函数的优劣,默认使用Equals和GetHashCode,可以通过制定IEqualityComparer<TKey>作为参数。
  • SortList<TKey, TValue>和SortedDictionary<TKey, TValue>,两者都是字典类,前者内部维护一个排序的数组,添加删除操作的事件复杂度为O(n),后者内部维护一个红黑树,添加删除操作事件复杂度为O(log n),但会消耗更多的堆内存,使用IComparer<TKey>作比较。
  • HashSet<T>,是不含值的Dictionary<,>,具有相同性能特性,并且所维护顺序一般与添加顺序无关。
  • SortedSet<T>,是没有值得SortedDictionary<,>,维护一个红黑树,添加删除和检查操作的事件复杂度为O(log n)。提供GetViewBetween方法返回介于原始集上下限之间的另一个SortedSet<T>,注意这是一个动态的视图,会随着原始集的改变而改变。尽管看起来很方便,但需要注意的是"天下没有免费的午餐",为保持内部一致性,操作的代价更大。
  • Queue<T>,构建一个环形缓冲区,实际维护一个基础数组,包含两个索引,分别记住入队和出队的位置(Slot),如果入队指针追上出队指针,则进行扩容。提供Enqueue、Dequeue、Peek等方法进行入队、出队、查看操作。
  • Stack<T>,其实现更简单,可以看做是一个提供Push、Pop、Peek操作的List<T>。

最后介绍并行集合,也就是线程安全的集合。(注意所有的并发类型都未实现IList<T>接口)

  • IProducerConsumerCollection<T>和BlockingCollection<T>,前者是生产者/消费者模型中数据存储的抽象,后者是其包装类,使用ConcurrentQueue<T>作为后台存储,提供ToArray方法获得集合当前状态快照,TryXXX方法允许有效的失败模式减少对锁的需求。(例如,当队列中只有一个项时,两个线程同时判断它是否有项,并且都返回true,这是一个线程执行了出队操作,而另外一个线程在执行出队操作时,将抛出异常,因而需要对验证队列是否有项操作和有项就出队操作作为一个整体,需要添加锁)
  • ConcurrentBag<T>,ConcurrentQueue<T>,ConcurrentStack<T>,它们是对IProducerConsumerCollection<T>的实现,其GetEnumerator()方法返回集合快照,迭代时可以改变集合,但该改变不会反应到迭代器中。
  • ConcurrentDictionary<TKey, TValue>, 实现了IDictionary<TKey, TValue>接口。支持并发的读写和线程安全的迭代,但不同是,其在迭代过程中对字典的改变不能确定是否反应到迭代器上。

小节:在日常工作中,当遇到需要并发操作非集合类型的全局变量时,需要使用锁来处理;而当是集合类型时,就需要使用对应的并行集合类来处理,其能很好的TPL协作在一起。尤其在使用非线程安全的字典类进行并发操作时,有时会出现死循环等情形,尤其需要注意。

Tip:where T:new()

参考文献

  1. Jon, Skeet. 深入理解C#(第3版)[M]. 北京:人民邮电出版社, 2014. 469-483
时间: 2024-11-05 18:35:44

快速入门系列--CLR--03泛型集合的相关文章

WPF快速入门系列(1)——WPF布局概览

一.引言 关于WPF早在一年前就已经看过<深入浅出WPF>这本书,当时看完之后由于没有做笔记,以至于我现在又重新捡起来并记录下学习的过程,本系列将是一个WPF快速入门系列,主要介绍WPF中主要的几个不同的特性,如依赖属性.命令.路由事件等. 在正式介绍之前,我还想分享下为什么我又要重新捡起来WPF呢?之前没有记录下来的原来主要是打算走互联网方向的,后面发现互联网方向经常加班,又累,有时候忙的连自己写了什么都不知道的,所以后面机缘巧合地进了一家外企,在外企不像互联网行业那样,比较清楚,有更多的时

快速入门系列--MVC--01概述

虽然使用MVC已经不少年,相关技术的学习进行了多次,但是很多技术思路的理解其实都不够深入.其实就在MVC框架中有很多设计模式和设计思路的体现,例如DependencyResolver类就包含我们常见的DI依赖注入概念和注册表模式(GetService)等内容,ExceptionFilter等过滤器就体现AOP的概念,整个MVC内置了一个IOC容器,基本上所有的框架类的对象都是通过这种方式来创建的.此外,一直觉得很j2ee的spring很棒,其实如果大家很熟悉EHAB(微软企业开发库)的话,就会发

WPF快速入门系列(4)——深入解析WPF绑定

一.引言 WPF绑定使得原本需要多行代码实现的功能,现在只需要简单的XAML代码就可以完成之前多行后台代码实现的功能.WPF绑定可以理解为一种关系,该关系告诉WPF从一个源对象提取一些信息,并将这些信息来设置目标对象的属性.目标属性总是依赖属性.然而,源对象可以是任何内容,可以是一个WPF元素.或ADO.NET数据对象或自定义的数据对象等.下面详细介绍了WPF绑定中的相关知识点. 二.绑定元素对象 2.1 如何实现绑定元素对象 这里首先介绍绑定最简单的情况——绑定元素对象,即数据源是一个WPF元

C语言快速入门系列(四)

C语言快速入门系列(四) C语言数组 ---------转载请注明出处:coder-pig 贴心小提示:如果图看不清晰可右键另存为,应该就很清晰了; 注意上面的代码都要自己过一遍哦! 本节引言: 经过我们前面三个系列的学习,我们对C语言有了一定的了解; 现在要你写这样一个代码应该不难吧: 输入五个学生的成绩,然后求出总和与平均值,打印出结果! 相信大家都会先定义五个变量,用来存储五个学生的成绩,然后再进行计算吧! 但是,假如要求的学生不是5个而是20个,50个或者更多,难道你又定义一堆变量么?

C语言快速入门系列(六)

C语言快速入门系列(五) C语言指针加强 本节引言: 上一节我们对C语言中的指针进行了初步的了解,学习了指针的定义,与普通变量 一维数组,二维数组,字符串之间的使用!在本节中我们将会学习一些新的知识点, 函数,结构体与共用体,枚举类型以及类型定义符typedef! 本节学习路线图: 本节正文 函数 ps:空函数就是什么都不做的函数,开发过程中不需要马上实现的,先写空函数!简单的空函数:void abc(){   } 结构体 共用体: 代码示例2: 建立一个共用体类型,当输入的时学生类型时,要求输

C语言快速入门系列(一)

C语言快速入门系列(一)  本系列引言: 本教程的宗旨是将C语言入门的内容进行关键知识点的提纯,将一些笼统的废话去除; 再进行压缩,然后将本章的关键知识点做成路线图的,可以更加方便地掌握学习的方向; 最后提供相关的代码示例以及详细注释,可以帮助学者更快地上手C语言! 如果对本教程有什么建议和缺点纰漏的,欢迎指出,不胜感激! 本节学习路线图: 正文: 1.计算机与程序设计语言的关系: 答:计算机是由硬件与软件系统组成,硬件==>物质基础;软件==>灵魂; 如果脱离了软件,计算机就只是一台什么都做

C语言快速入门系列(五)

C语言快速入门系列(五) C语言指针初涉                                           ------转载请注明出处:coder-pig 本节引言: 上一节我们对C语言复合数据类型中的数组进行了解析,在本节中,我们会对C语言复合数据类型中的 重点,C语言的灵魂-----指针进行学习!使用指针的好处:利用指针可以表示与使用复杂的数据结构; 更加方便地使用我们的数组与字符串;可以像汇编语言一样直接处理内存单元地址;可以动态地进行内存空间 分配,C语言指针是重点,同

C语言快速入门系列(九)

C语言快速入门系列(九)                                               ---转载请注明出处:coder-pig C语言知识点拾遗 本节引言: C语言系列已经接近尾声了,在前面八节的学习中,我们学会了C的基本语法,基本数据类型, 三种程序结构(顺序,判断,循环),数组,函数,指针,结构体,共用体,位运算,文件等内容, 本节将对前面没有讲的C的遗漏知识点进行补充,当然发现有那些的遗漏的知识点也会进行更新! 谢谢大家一直以来的支持,说了这么多的理论,缺的

C语言快速入门系列(七)

C语言快速入门系列(七) C语言指针进阶 本章引言: 在前面第5节中我们对C语言的指针进行了初步的学习理解;作为C语言的灵魂, C指针肯定没那么简单,在这一节中,我们将会对指针进行进一步的学习,比如二级指针, 指针数组,内存分配和const修饰指针常量等!下面就请大家跟随笔者的脚步,对 C指针神秘的一面进行进一步的解析吧! 本节学习路线图: 函数与指针: ①指针作为函数的形参: ②指向函数的指针: ③指针函数: ④带参数的主函数 ps:该代码的运行:先要编译生成exe文件后,来到exe所在文件目

C语言快速入门系列(三)

C语言快速入门系列(三) 结构化的程序设计 -----------------------------------转载请注明出处:coder-pig 本节引言: 在前面的学习中,我们对C语言的基本语法进行了了解,可以暂时理解成我们学了单词; 现在要做得就是学语法,也就是算法;就是构成一个一个基本的程序! 在这一节中我们要学习的是C语言中的输入输出,以及程序的三种结构(顺序,选择,循环结构) 本节学习路线图: 正文: 1.字符输入/输出函数 2.格式输入/输出函数 跟前面的单个字符的输入输出不同,