集合是我们编程时候常用的类库,本文主要讨论具体每个类型的区别,每个集合对应的时间复杂度。先上一个时间复杂度图:

C#集体类型( Collections in C#)

  集合是.NET FCL(Framework Class Library)中很重要的一部分(.net core 中是corefx),也是我们开发当中最常用到的功能之一,几乎是无处不在。俗话说知其然,知其所以然,平常看到IEnumerable,IEnumerator,ICollection是不是知道他们之间各自的区别?除了List和Dictionary以外,你还用过哪些其它的集合类?废话少说,今天我们就来看一些这些定义集合类的接口以及他们的实现。

集合接口

先来看一下,FCL为我们提供了哪些接口:

IEnumerable 和IEnumberator

public interface IEnumerator
{
bool MoveNext();
object Current { get; }
void Reset();
}

IEnumerator定义了我们遍历集合的基本方法,以便我们可以实现单向向前的访问集合中的每一个元素。而IEnumerable只有一个方法GetEnumerator即得到遍历器

public interface IEnumerable
{
IEnumerator GetEnumerator();
}

注意:我们经常用的foreach即是一种语法糖,实际上还是调用Enumerator里面的Current和MoveNext实现的遍历功能。

List<string> list = new List<string>()
{
"Jesse",
"Chloe",
"Lei",
"Jim",
"XiaoJun"
};
// Iterate the list by using foreach
foreach (var buddy in list)
{
Console.WriteLine(buddy);
}
// Iterate the list by using enumerator
List<string>.Enumerator enumerator = list.GetEnumerator();
while (enumerator.MoveNext())
{
Console.WriteLine(enumerator.Current);
}

上面的代码中用到的foreach和enumerator到IL中最后都会被翻译成enumerator的MoveNext和Current。

IEnumerable是一个很有用的接口,实现它的好处包括:

1 foreach语句

2 作为一个标准的集合类与其它类库进行交互

3 满足更复杂的集合接口的需求

4 支持集合初始化器

当然实现的方法也有很多,如下:

1 我们集合是通过封装其它集合类而来的,那么我们可以直接返回这个集合的enumerator

2 yield return 来返回

3 我们自己的IEnumerator来实现

这里给大家演示一下如何通过yield来实现返回enumerator

public class BuddyList : IEnumerable
{
private string[] data= new string[]
{
"Jesse",
"Chloe",
"Lei",
"Jim",
"XiaoJun"
};
public IEnumerator GetEnumerator()
{
foreach (var str in data)
{
yield return str;
}
}
}
var myBuddies= new BuddyList();
foreach (var str in myBuddies)
{
Console.WriteLine(str);
}

ICollection<T>和ICollection

从最上面第一张图我们可以知道,ICollection是直接继承自IEnumerable。而实际上也是如此,我们可以说ICollection比IEnumerable多支持一些功能,不仅仅只提供基本的遍历功能,还包括:

1 集合和元素个数

2 元素的下标

3 是否存在

4 元素到未尾

5 元素等等。。。

ICollection 与ICollection<T> 略有不同,ICollection不提供编辑集合的功能,即Add和Remove。包括检查元素是否存在Contains也不支持。

IList<T>和IList

IList则是直接继承自ICollection和IEnumerable。所以它包括两者的功能,并且支持根据下标访问和添加元素。IndexOf, Insert, RemoveAt等等。我们可以这样说,IEnumerable支持的功能最少,只有遍历。而ICollection支持的功能稍微多一点,不仅有遍历还有维护这个集合的功能。而IList是最全的版本。

IReadOnlyList<T>

这个是在Framework4.5中新增的接口类型,可以被看作是IList<T>的缩减版,去掉了所有可能更改这个集合的功能。比如:Add, RemoveAt等等。

IDictionary<TKey,TValue>

IDictionary提供了对键值对集合的访问,也是继承了ICollection<T>和IEnumerable,扩展了通过Key来访问和操作数据的方法。

关联性泛型集合类

关联性集合类即我们常说的键值对集合,允许我们通过Key来访问和维护集合。我们先来看一下 FCL为我们提供了哪些泛型的关联性集合类:

SortedDictionary<TKey,TValue>

SortedList<TKey,TValue>

Dictionary<TKey,TValue>

Dictionary<TKey,TValue>

  Dictionary<TKey,TValue>可能是我们最常用的关联性集合了,它的访问,添加,删除数据所花费的时间是所有集合类里面最快的,因为它内部用了Hashtable作为存储结构,所以不管存储了多少键值对,查询/添加/删除所花费的时间都是一样的,它的时间复杂度是O(1)

  Dictionary<TKey,TValue>优势是查找插入速度快,那么什么是它的劣势呢?因为采用Hashtable作为存储结构,就意味着里面的数据是无序排列的,所以想按一定的顺序去遍历Dictionary<TKey,TValue>里面的数据是要费一点工夫的。

  作为TKey的类型必须实现GetHashCode()和Equals()或者提供一个IEqualityComparer,否则操作可能会出现问题。

SortedDictioanry<TKey,TValue>

  SortedDictionary<TKey,TValue>和Dictionary<TKey,TValue>大致上是类似的,但是在实现方式上有一点点区别。SortedDictionary<TKey,TValue>用二叉树作为存储结构的。并且按key的顺序排列。那么这样的话SortedDictionary<TKey,TValue>的TKey就必须要实现IComparable<TKey>。如果想要快速查询的同时又能很好的支持排序的话,那就使用SortedDictionary吧

SortedList<TKey,TValue>

  SortedList<TKey,TValue>是另一个支持排序的关联性集合。但是不同的地方在于,SortedList实际是将数据存存储在数组中的。也就是说添加和移除操作都是线性的,时间复杂度是O(n),因为操作其中的元素可能导致所有的数据移动。但是因为在查找的时候利用了二分搜索,所以查找的性能会好一些,时间复杂度是O(log n)。所以推荐使用场景是这样地:如果你想要快速查找,又想集合按照key的顺序排列,最后这个集合的操作(添加和移除)比较少的话,就是SortedList了。

非关联性泛型集合类

非关联性集合就是不用key操作的一些集合类,通常我们可以用元素本身或者下标来操作。FCL主要为我们提供了以下几种非关联性的泛型集合类。

List<T>

LinkedList<T>

HashSet<T>

SortedSet<T>

Stack<T>

Queue<T>

List<T>

泛型的List 类提供了不限制长度的集合类型,List在内部维护了一定长度的数组(默认初始长度是4),当我们插入元素的长度超过4或者初始长度的时候,会去重新创建一个新的数组,这个新数组的长度是初始长度的2倍(不永远是2倍,当发现不断的要扩充的时候,倍数会变大),然后把原来的数组拷贝过来。所以如果知道我们将要用这个集合装多少个元素的话,可以在创建的时候指定初始值,这样就避免了重复的创建新数组和拷贝值。

另外的话由于内部实质是一个数组,所以在List的未必添加数据是比较快的,但是如果在数据的头或者中间添加删除数据相对来说更低效一些因为会影响其它数据的重新排列。

LinkedList<T>

LinkedList在内部维护了一个双向的链表,也就是说我们在LinkedList的任何位置添加或者删除数据其性能都是很快的。因为它不会导致其它元素的移动。一般情况下List已经够我们使用了,但是如果对这个集合在中间的添加删除操作非常频繁的话,就建议使用LinkedList。

HashSet<T>

HashSet是一个无序的能够保持唯一性的集合。我们也可以把HashSet看作是Dictionary<TKey,TValue>,只不过TKey和TValue都指向同一个对象。HashSet非常适合在我们需要保持集合内元素唯一性但又不需要按顺序排列的时候。

HashSet不支持下标访问。

SortedSet<T>

SortedSet和HashSet,就像SortedDictionary和Dictionary一样,还记得这两个的区别么?SortedSet内部也是一个二叉树,用来支持按顺序的排列元素。

Stack<T>

后进先出的队列
不支持按下标访问

Queue<T>

先进先出的队列
不支持按下标访问

推荐使用场景

非泛型类集合

  泛型集合类是在.NET2.0的时候出来的,也就是说在1.0的时候是没有这么方便的东西的。现在基本上我们已经不使用这些集合类了,除非在做一些和老代码保持兼容的工作的时候。来看看1.0时代的.NET程序员们都有哪些集合类可以用。

ArraryList后来被List<T>替代。

HashTable后来被Dictionary<TKey,TValue>替代。

Queue后来被Queue<T>替代。

SortedList后来被SortedList<T>替代。

Stack后来被Stack<T>替代。

线程安全的集合类

ConcurrentQueue线程安全版本的Queue

ConcurrentStack线程安全版本的Stack

ConcurrentBag线程安全的对象集合

ConcurrentDictionary线程安全的Dictionary

BlockingCollection

对于线程安全集合,我在《C#异步编程(五)异步的同步构造》里面的并发集合内容有深入的讨论。

参考博文:

《常用数据结构及复杂度》

《C#集合类型大盘点》

C#进阶之路(八)集合的应用的更多相关文章

  1. Scala进阶之路-高级数据类型之集合的使用

    Scala进阶之路-高级数据类型之集合的使用 作者:尹正杰 版权声明:原创作品,谢绝转载!否则将追究法律责任. Scala 的集合有三大类:序列 Seq.集 Set.映射 Map,所有的集合都扩展自 ...

  2. 【SSH进阶之路】Hibernate映射——多对多关联映射(八)

    上篇博文[SSH进阶之路]Hibernate映射——一对多关联映射(七),我们介绍了一对多关联映射,它是多对多关联映射的基础. 多对多映射是现实生活中最常见的映射,也是最容易理解的映射.废话少说,直接 ...

  3. 【进阶之路】多线程条件下分段处理List集合的几种方法

    这两个月来因为工作和家庭的事情,导致一直都很忙,没有多少时间去汲取养分,也就没有什么产出,最近稍微轻松了一点,后续的[进阶之路]会慢慢回到正轨. 开门见山的说,第一次接触到多线程处理同一个任务,是使用 ...

  4. 【SSH进阶之路】一步步重构容器实现Spring框架——彻底封装,实现简单灵活的Spring框架(十一)

    文件夹      [SSH进阶之路]一步步重构容器实现Spring框架--从一个简单的容器開始(八)      [SSH进阶之路]一步步重构容器实现Spring框架--解决容器对组件的"侵入 ...

  5. SSH进阶之路

    [SSH进阶之路]Hibernate基本原理(一)       在开始学Hibernate之前,一直就有人说:Hibernate并不难,无非是对JDBC进一步封装.一句不难,难道是真的不难还是眼高手低 ...

  6. 【SSH进阶之路】Hibernate系列——总结篇(九)

    这篇博文是Hibernate系列的最后一篇,既然是最后一篇,我们就应该进行一下从头到尾,整体上的总结,将这个系列的内容融会贯通. 概念 Hibernate是一个对象关系映射框架,当然从分层的角度看,我 ...

  7. PHP进阶之路 -- 01 PHP基础语法

    PHP进阶之路 --  PHP基础语法 windows环境下php环境 php定界符  php变量  php数据类型 数据类型转换 检测数据类型 php中三种输出方式 php字符集设置 php常量 p ...

  8. Scala进阶之路-统计商家id的标签数以及TopN示例案例分析

    Scala进阶之路-统计商家id的标签数以及TopN示例案例分析 作者:尹正杰 版权声明:原创作品,谢绝转载!否则将追究法律责任. 一.项目需求 将“temptags.txt”中的数据进行分析,统计出 ...

  9. Scala进阶之路-尾递归优化

    Scala进阶之路-尾递归优化 作者:尹正杰 版权声明:原创作品,谢绝转载!否则将追究法律责任. 递归调用有时候能被转换成循环,这样能节约栈空间.在函数式编程中,这是很重要的,我们通常会使用递归方法来 ...

  10. GO语言的进阶之路-面向对象编程

    GO语言的进阶之路-面向对象编程 作者:尹正杰 版权声明:原创作品,谢绝转载!否则将追究法律责任. 当你看完这篇文章之时,我可以说你的Golang算是入门了,何为入门?就是你去看Docker 源码能看 ...

随机推荐

  1. PHP fread 文件系统函数

    定义和用法 fread - 读取文件(可安全用于二进制文件) 版本支持 PHP4 PHP5 PHP7 支持 支持 支持 语法 fread ( resource $handle , int $lengt ...

  2. Linux常见目录说明

    常见目录说明 目录 应放置档案内容 /bin/ 存放二进制可执行文件,系统的命令(ls,cat,mkdir等),是/usr/bin/目录的软链接. /sbin/ 存放系统命令,超级用户可以执行.是/u ...

  3. Smobiler针对百度文字识别SDK动态编译与运行

    下载百度ocr 在百度ocr平台下载android资源文档 文档地址:https://ai.baidu.com/docs#/OCR-Android-SDK/top sdk下载地址:http://ai. ...

  4. Mybatis书写

    Mybatis设置主键和自增 方法1: <insert id="insert" parameterType="Person" useGeneratedKe ...

  5. Python—虚拟环境的创建与管理(virtualenv与virtualenvwrapper)

    virtualenv 1.安装virtualenv包 [root@localhost ~]# pip install virtualenv 2.创建虚拟环境,位置不固定,可以在项目的目录下,也可以不在 ...

  6. 抓包工具Fiddler的简单使用

    HTTP代理 http代理,就是代理客户机的http访问,主要代理浏览器访问页面 代理服务器是介于浏览器和web服务器之间的一台服务器,有了它之后,浏览器不是直接到Web服务器去取回网页而是向代理服务 ...

  7. 渗透测试学习 十九、 XSS跨站脚本漏洞详解 续2

    二阶注入环境搭建 74cms 3.4 直接将源码放在PHPstudy的www路径下,在地址栏中输入127.0.0.1回车 然后进入网站首页,在填写简历里面存在二阶注入 先注册一个账号 创建简历 前面的 ...

  8. c# WF 第7节 对控件的基本操作

    本节内容: 1:修改控件的大小 2:控件的顺序 3:控件的对齐 4:移动button实例 1: 修改控件的大小 第一种: 第二种 : 一次操作多个控件: 选择一个控件.按住ctrl -- 再选择一个控 ...

  9. Tensorflow之MNIST手写数字识别:分类问题(2)

    整体代码: #数据读取 import tensorflow as tf import matplotlib.pyplot as plt import numpy as np from tensorfl ...

  10. DOM的重绘和回流及代码性能优化

    1.DOM的重绘和回流Repaint&Reflow 1.1重绘:元素样式的改变(但宽高.大小.位置等不变) 如outline.visibility.color.background-color ...