C#集合通论

前言

  写这篇文章的最初动力是来自于一次笔试经历。有一道笔试题大概是这样的:程序使用一个txt文件来存储操作记录。存储记录是多行字符串,每一行代表一次操作记录,格式如下:用户名+操作事项名称+操作时间。现在假设这个txt文件已经非常大了,要求对这个文件做一些处理(具体记不太清了,接近于一些逻辑处理和增删改)。毫无疑问,对于txt文件来说,要对之中的数据进行处理,首先要把数据读入内存,这就涉及到选择何种数据结构的问题了。基于自己的常规思维,我不加思索就选择了自定义类的List泛型存储数据。之后再与面试官交流的时候,他给出了用Dictionary泛型的解决方案。由于自己的认知局限,当时没听明白面试官的具体解释,导致这道问题的讨论就成了单方面的阐述,失去了双方的交流。面试也是发现问题的一种途径。对于集合知识,自身确实存在认知匮乏的问题,而在许多程序中,选择合适的数据结构往往是决定整个算法或代码是否简洁优雅的关键所在,如果对集合都不熟悉的话,那么谈何选择合适的数据结构呢?如果你也存在和我一样的问题,可以尝试着继续读这篇文章,或许能给你带来帮助。

  这篇文章讨论的主题是集合,重点在于分析集合的区别和联系,加深对集合的认知与使用,熟悉常用C#类库有关集合类的组织结构。

数组

  论集合,不得不谈的第一项就是数组。C#数组需要声明元素类型,元素类型可以是值类型也可以是引用类型,数组是静态类型,初始化必须指定个数大小,且创建后的数组是连续存放在内存中的。声明方式如下所示:

int[] intArray = new int[10];       //整型数组
string[] stringArray = new string[10]; //字符串数组   
Random[] randArray = new Random[10];   //类数组

  数组是从Array隐式派生的,这是由编译器完成的,Array类被组织在System命名空间下。

Array myArray1 = new int[10];
Array myArray2 = new string[10];
Array myArray3 = new Random[10];

 数组是引用类型,初始化后分配在堆上。

System.Collections

  1. System.Collections 组织空间

      

 集合类型根据自身的需求实现了左边接口中的一个或多个,按照实现接口大概可以分为三种:有序集合(ICollection),索引集合(IList),键式集合(IDictionary)。

  2. 通用接口

public interface IEnumerator
{bool MoveNext();object Current { get; }void Reset();
}public interface IEnumerable
{   IEnumerator GetEnumerator();
}public interface ICollection : IEnumerable
{int Count { get; }bool IsSynchronized { get; }object SyncRoot { get; }void CopyTo(Array array, int index);
}public interface IList : ICollection, IEnumerable
{bool IsFixedSize { get; }bool IsReadOnly { get; }object this[int index] { get; set; }int Add(object value);void Clear();bool Contains(object value);int IndexOf(object value);void Insert(int index, object value);void Remove(object value);void RemoveAt(int index);
}public interface IDictionary : ICollection, IEnumerable
{bool IsFixedSize { get; }bool IsReadOnly { get; }object this[object key] { get; set; }ICollection Keys { get; }ICollection Values { get; }void Add(object key, object value);void Clear();bool Contains(object key);IDictionaryEnumerator GetEnumerator();void Remove(object key);
}
Collections常用接口实现

   集合类型都实现了IEnumerable接口,从而可以使用foreach迭代。

   实现了ICollectoin接口的集合类表明集合中的元素是有先后顺序的。

   IList接口继承了ICollection接口,实现了IList接口的集合类不止表明集合中的元素是有先后顺序,而且表明集合类可以通过下标访问的方式访问集合元素。

   IDictionary接口也继承了ICollection接口,实现了IDicionary接口的集合类可以通过下标key访问的访问方式访问集合元素。

  3. ArrayList

  Array是静态分配的,意味着创建数组后的大小不能改变,然而实际应用中,我们很多时候无法在一开始确定数组的大小,这样便需要一种能动态分配的数组类型,ArrayList就是为此而生的。ArrayList主要实现的接口有IList,所以这是一个索引集合。

//声明ArrayList
ArrayList myArray = new ArrayList();//插入元素 可以为值类型 也可以为引用类型
myArray.Add(1);
myArray.Add("hello");
myArray.Add(new Random());int i = (int)myArray[0];
string str = (string)myArray[1];
Random rand = (Random)myArray[2];

  使用Reflector查看ArrayList的底层实现,可以看到动态数组其实是由一个object[] _items维系着的,这就解释了为什么集合的元素可以为值类型也可以为引用类型。这样的底层实现貌似灵活性很高,集合可以容纳异构类型,然而却带来了性能上的问题,因为当插入值类型的时候,就存在隐式装箱操作,而当把元素还原为值类型变量的时候,又发生了一次显式拆箱操作,如果这种装箱拆箱存在上千万次,那么程序的性能是要大打折扣的。同时要注意一点的是object类型可以强制转化为任何类型,这是说编译器不会检查object强制转换的类型,如果无法转换的话,这必须等到运行时才能确定出错,这就是类型安全问题。

  所以应该尽量避免使用ArrayList。

  4. Stack 和 Queue

  对于栈和队列这两种经典的数据结构,C#也将它们组织在System.Collections命名空间中。Stack是后进先出的结构,Queue是先进先出的结构,这两者主要实现的接口有ICollection,表示它们都是有序集合,应注意到这两者都不可以使用下标访问集合元素。这两者的底层实现都是由一个object[] _array维系着,都存在着装箱拆箱的性能问题和类型安全问题,所以应该尽量避免直接使用它们。

  5. HashTable

    前面我们提到的集合类都属于单元素集合类,实际应用中我们需要一种键值对的形式存储数据,即集合类中存储的不再是单个元素,而是key-value两个元素,HashTable就是为此而生的。HashTable实现了IDictionary接口。

//创建一个HashTable实例
Hashtable hashDict = new Hashtable();//往容器中加入key-value
hashDict.Add("a", "hello");
hashDict.Add("b", "hello");
hashDict.Add("c", "go");
hashDict.Add(4, 300);//通过下标key获取value
string str1 = hashDict["a"].ToString();
string str2 = (string)hashDict["b"];
int i4 = (int)hashDict[4];

   HashTable中的key关键字必须唯一,不能重复。如果深入到HashTable的底层实现,应该可以清楚的看到key和value是结构体bucket数组维护着,bucket中key和value的实现也是object,所以存在着与ArrayList,Stack,Queue同样的问题,应该尽量避免使用HashTable。

 为什么键值集合类要命名为HashTable?

     从HashTable的命名来看,我们可以断定说键值集合跟Hash必定存在某种联系。哈希又称为散列,散列技术是在记录的存储位置和它的关键字之间建立一个确定的对应关系f,使得每个关键字key对应一个存储位置f(key),f又称为散列函数。HashTable实现了IDictionary接口,表明HashTable可以通过下标key访问的方式获取数组元素,即     HashTable[key],这与实现了IList接口的集合类的数字下标访问存在着明显的不同。那么如何通过key快速定位得到value呢?我相信通过前面的铺垫大伙都知道是什么回事了,对,就是哈希函数的运用。具体可以参考文章http://www.cnblogs.com/abatei/archive/2009/06/23/1509790.html

  6. SortedList

  在控制台应用程序下运行以下代码并观察结果:

Hashtable hashDict = new Hashtable();
hashDict.Add("key1", "1");
hashDict.Add("key2", "2");
hashDict.Add("key3", "3");
hashDict.Add("key4", "4");foreach (string key in hashDict.Keys)
{Console.WriteLine(hashDict[key]);
}

      我们可以看到这里并没有按照预期的Key顺序输出Value。也就是说HashTable是不按照key顺序排序的,具体原理可以参考HashTable的源码。SortedList很好地解决了键值集合顺序输出的问题。

//创建一个SortedList实例
SortedList sortList = new SortedList();//往容器中加入元素
sortList.Add("key1", 1);
sortList.Add("key2", 2);
sortList.Add("key3", 3);
sortList.Add("key4", 4);//获取按照key排序的第index个Key
string str1 = sortList.GetKey(0).ToString();
Console.WriteLine(str1);
//获取按照key排序的第index个Value
int i1 = (int)sortList.GetByIndex(0);
Console.WriteLine(i1.ToString());
//下标key访问
string str2 = sortList["key2"].ToString();
Console.WriteLine(str2);//遍历sortList
foreach (DictionaryEntry item in sortList)
{Console.WriteLine(item.Key);Console.WriteLine(item.Value);
}

   SortedList实现了IDictionary接口,所以可以使用下标key访问元素的形式,同时要求key必须唯一。

 我们知道HashTable通过使用哈希函数通过key快速找到存储位置,那么SortedList又是如何实现下标key访问元素?

  SortedList的底层实现与HashTable有着本质的区别。SortedList中使用object[] keys 和 object[] values 两个对象数组分别来存储key和value,要求实现能按照key有序输出,在下标key访问的时候就无法使用Hash函数了,所以SortedList虽然也是键值集合,但与Hash却没有任何联系。通过查看SortedList的底层代码,原来它的实现是二分查找(BinarySearch),也就是说要求key是有序排列的,在查找的时候进行二分比较搜索,找到对应的index,从而返回values[index]。

 那么如何在HashTable和SortedList中做出选择?

  如果需要实现按照key有序输出,那么毫无疑问就要选择SortedList了。如果不需要按照key有序输出,在小数据量的情况下,两者选择任何一个性能都应该差不多,但大数据量的情况下,则更应该选择HashTable。为什么呢?理由有两点。1.HashTable的key下标访问更直接更快。通过上面分析我们知道SortedList的key下标访问是由二分查找实现的,实现的时间复杂度为O(log n),而Hash函数的时间复杂度为O(1),HashTable的实现更优。2.SortedList要求key有序,这意味着在插入的时候必须适当地移动数组,从而达到有序的目的,所以存在性能上的消耗,HashTable的实现更优。

   SortedList也并没有走出装箱拆箱性能和类型安全的圈子,所以应该尽量避免直接使用它。

System.Collections.Generic

  1. System.Collections.Generic 组织空间

  

System.Collections.Generic是.NET 2.0新增的一个命名空间。C#1中的集合类型都存在着装箱拆箱的性能问题以及潜在的类型安全问题,丑陋的设计必须得到改进,于是C#2就引入了泛型这个概念。

  2. 何为集合泛型

  新的命名空间下,可以看到接口或集合类后都携带了<T>或<TKey,TValue>。很明显,携带<T>对应的是单元素集合,携带<TKey,TValue>对应的是键值集合。那么泛型又是如何工作的呢?来看一下其编译过程吧:初次编译时,首先生成IL代码和元数据,T(TKey,TValue)只是作为类型占位符,不进行泛型类型的实例化;在进行JIT编译时,将以实际类型替换IL代码和元数据中的T占位符,并将其转换为本地代码,下一次对该泛型类型的引用将使用相同的本地代码。

  泛型即解决了装箱拆箱的性能问题,又解决了潜在的类型转换安全问题,所以在实际应用中,推荐使用泛型集合代替非泛型集合。

  3. 泛型集合与非泛型集合的对应 

  ArrayList => List<T>  新的泛型集合去掉了Array前缀。

  Stack,Queue => Stack<T>,Queue<T>

  HashTable => Dictionary<TKey,TValue> 新的泛型键值集合的命名放弃了HashTable,但其内部实现原理还是和HashTable有很大相似的。

  SortedList => SortedList<TKey,TValue> | SortedDictionary<TKey,TValue>

 如何区分SortedList<TKey,TValue> 和 SortedDictionary<TKey,TValue>?

   SortedList<TKey,TValue>的底层实现基本是按照SortedList,下标key访问是二分查找的O(log n),插入和移除运算复杂度是O(n)。而SortedDictionary<TKey,TValue> 底层实现是二叉搜索树,下标key访问也为O(log n),插入和移除运算复杂度是O(log n)。所以SortedList<TKey,TValue>使用的内存会比SortedDictionary<TKey,TValue>小,SortedDictionary<TKey,TValue>在插入和移除元素的时候更快。

转载于:https://www.cnblogs.com/teroy/p/3134666.html

本文来自互联网用户投稿,该文观点仅代表作者本人,不代表本站立场。本站仅提供信息存储空间服务,不拥有所有权,不承担相关法律责任。如若转载,请注明出处:http://www.mzph.cn/news/575924.shtml

如若内容造成侵权/违法违规/事实不符,请联系多彩编程网进行投诉反馈email:809451989@qq.com,一经查实,立即删除!

相关文章

Shell 脚本案例实战 [4]

for循环结构for 循环结构语句1.for循环结构&#xff1a;语法&#xff1a;for 变量名 in 变量取值列表do指令…done提示&#xff1a;在此结构中“in 变量取值列表”可省略&#xff0c;省略时相当于in “$”&#xff0c;使用for i 就相当于使用for i in “$”2.C语言型for循环结构…

深度优化LNMP之PHP

PHP缓存加速介绍1.操作码介绍及缓存原理当客户端请求一个php程序时&#xff0c;服务器的PHP引擎会解析该PHP程序&#xff0c;并将其编译为特定的操作码文件&#xff08;Operate Code,opcode&#xff09;该文件是执行PHP代码后的一种二进制表示形式。默认情况下&#xff0c;这个…

PHP服务Fcgi进程及PHP解析优化

1、PHP引擎缓存加速常见四种软件&#xff1a;1.eAccelerator2.Zendcache3.xcache4.apc5.zendopcache php5.5自带2、使用tmpfs作为缓存加速缓存的文件目录 [rootweb02 ~]# mount -t tmpfs tmpfs /dev/shm -o size256m[rootweb02 ~]# mount -t tmpfs /dev/shm/ /tmp/eaccelerator…

电路 晶振频率_都说晶振是电路的心脏,你真的了解它吗?

之所以说晶振是数字电路的心脏&#xff0c;就是因为所有的数字电路都需要一个稳定的工作时钟信号&#xff0c;最常见的就是用晶振来解决&#xff0c;可以说只要有数字电路的地方就可以见到晶振。常见种类我们常说的晶振&#xff0c;包含两种。一种需要加驱动电路才能产生频率信…

ios 数组中的字典排序_利用数组和字典,实现按指定规则的排序

大家好&#xff0c;今日我们继续讲解数组与字典解决方案&#xff0c;今日讲解第47讲&#xff1a;利用字典和数组&#xff0c;实现按指定规则的排序。随着字典讲解的深入,我们发现字典真的很神奇,在VBA代码中,给人以十分清爽的感觉,在这套数组与字典解决方案中,我会尽可能的把经…

MVC3学习:利用mvc3+ajax实现登录

用到的工具或技术&#xff1a;vs2010,EF code first,JQuery ajax,mvc3。 第一步&#xff1a;准备数据库。 利用EF code first&#xff0c;先写实体类&#xff0c;然后根据实体类自动创建数据库&#xff1b;或者先创建数据库&#xff0c;再写实体类&#xff0c;都可以。如果实体…

vue获取tr内td里面所有内容_vue 项目学习

首先页面的整体内容结构以及package.json 里面的内容package.jsonrouter.js 路由功能import Vue from vue import Router from vue-router import Login from /login;Vue.use(Router) let router new Router({routes: [{path: /,redirect: {name: Login},},{path: /Login,na…

ubuntu中解压rar文件遇到乱码的解决方法

如上图所示&#xff0c;在用ubuntu的时候经常会遇见rar压缩文件打开出现乱码&#xff0c;解压的时候也会出现无效的编码等错误。 解决方法是用 sudo apt-get remove rar 卸载rar 然后用 sudo apt-get instal unrar 安装unrar 然后就可以解决这个问题了。 个人理解rar是用来压缩…

kmeans中的k的含义_硬质合金中P、M、K、N、S、H六大字母含义详解

数控技术在线订单 | 技术 | 干货 | 社群关注可加入机械行业群&#xff01;关注P类:硬质合金中&#xff0c;P类产品的切削范围是指碳钢&#xff0c;铸钢&#xff0c;包括0.25-0.25%C淬火和调质&#xff0c;易切钢包含退火与淬火调质&#xff0c;低碳合金钢含金元素少于5%的范围&…

gitlab集成ldap用户后,禁用原来的账户体系进行拉取代码

gitlab在集成ldao用户后&#xff0c;经过测试&#xff0c;用户可以还可以通过原来的账号体系进行项目代码的拉取&#xff0c;需要把原来的账号体系的拉取功能关闭&#xff0c;使用ldap的账号体系进行工作开展。 方法如下&#xff1a; 在管理员-设置-通用-登录限制里把 Allow…

ffmpeg 分辨率 压缩_用GPU加速FFmpeg中的超分辨率功能

1. 简要回顾首先简单复述一下FFmpeg中对深度学习的支持情况&#xff0c;如上图所示&#xff0c;FFmpeg在libavfilter中支持基于深度学习的filter&#xff0c;目前已经支持sr, derain和dnn_processing等filter&#xff0c;其中&#xff0c;dnn_processing是一个通用的filter&…

mysql存储过程输入参数拆分_一文看懂mysql数据库存储过程、函数、视图、触发器、表...

概述抽空总结一下mysql的一些概念性内容&#xff0c;涉及存储过程、函数、视图、触发器等。一、查看存储过程、函数、视图、触发器、表1、存储过程select * from mysql.proc where typePROCEDURE;show procedure status; show create procedure proc_name; //存储过程定义2、函…

oracle vm发现无效设置_Oracle数据库编译失效对象相关命令总结大全,值得收藏

概述在日常数据库维护过程中&#xff0c;我们会发现数据库中一些对象(包Package、存储过程Procedure、函数Function、视图View、同义词.....)会失效,呈现无效状态(INVALID)。有时候需要定期检查数据库中存在哪些失效对象&#xff0c;对于存在异常的对象需要重新编译&#xff0c…

WCF-001:WCF的发布

随着“云”时代的到来&#xff0c;“云”已经无处不在了。什么是“云”&#xff0c;无非就是利用互联网强大的功能建立多个服务器&#xff0c;然后再利用互联网的传输数据的特点将数据从某个服务器中读取出来或者将你的数据上传上去。当然这个服务器不是一定就是传统意义的服务…

在线编辑_水墨-在线 Markdown 编辑器

水墨-在线 Markdown 编辑器基于 Spring-boot、FreeMarker、layui、Vditor 构建的一款在线 所见即所得的 Markdown 编辑器。水墨-在线 Markdown 编辑器。本人使用 Vditor 编辑器时日已久&#xff0c;眼看着其功能日益强大&#xff0c;特此基于 Vditor 构建一款 Web 编辑器&#…

退出出库复核是什么意思_细思极恐!为什么是黄晓明退出而不是李菲儿?因为女方是芒果艺人...

本文转载自&#xff1a;娱评人吴清功序言&#xff1a;《乘风破浪的姐姐》第二季即将接档《姐姐的爱乐之程》&#xff0c;于每周五晚上十点档播出。2021年1月5日&#xff0c;《乘风破浪的姐姐2》第一次公演举行&#xff0c;选手李菲儿和发起人黄晓明有亲密互动&#xff0c;李菲儿…

ie6 ie7下使用clear不能将浮动的元素换行问题

在主流浏览器中使用clear方法可以轻松完成浮动元素的换行。 例如&#xff1a; <!DOCTYPE html PUBLIC "-//W3C//DTD XHTML 1.0 Transitional//EN" "http://www.w3.org/TR/xhtml1/DTD/xhtml1-transitional.dtd"> <html xmlns"http://www.w3.o…

传值类型_Java内存管理:Stackoverflow问答-Java是传值还是传引用(十一)

勿在流沙筑高台&#xff0c;出来混迟早要还的。做一个积极的人编码、改bug、提升自己我有一个乐园&#xff0c;面向编程&#xff0c;春暖花开&#xff01;本文导图&#xff1a;一、由一个提问引发的思考在Stack Overflow 看到这样一个问题&#xff1a;Is Java “pass-by-refere…

java位运算(转)

位移动运算符: <<表示左移, 左移一位表示原来的值乘2. 例如&#xff1a;3 <<2(3为int型) 1&#xff09;把3转换为二进制数字0000 0000 0000 0000 0000 0000 0000 0011&#xff0c; 2&#xff09;把该数字高位(左侧)的两个零移出&#xff0c;其他的数字都朝左平移2位…

可以批量转modis投影_SNAP批量处理Sentinel2数据

Sentinel-2数据以其高时空分辨率获得了越来越多的应用&#xff0c;然而由于Sentinel-2数据空间分辨率高&#xff0c;光谱分辨率高&#xff0c;数据量也较Landsat数据大幅上升。好在ESA SNAP软件批处理功能也很强大&#xff0c;可以支撑起大数据量的应用。下面就介绍一下如何使用…