以List为例浅谈C#的学习方法

发表于:2018-1-04 10:06

字体: | 上一篇 | 下一篇 | 我要投稿

 作者:布拉德皮特    来源:博客园

#
DoNet
分享:
  前言:关于学习方法的讨论其实是个比较模糊的概念,对于List的介绍的资料其实已经很多了,但是一般是介绍List本身,我打算分享的是,以温故List为例,来获取新知识的这么一个过程。这里的新知识也不是什么新知识,依旧是算法、泛型、迭代器和GC回收机制等相关术语,只不过通过对List再分析,对这些东西的运用有了些新的认知,我要分享的就是再分析的这么一个过程,姑且允许我将其称为学习方法吧,最起码它是比较适合我的一种学习方法。另外,如果对List已经了解很深或者兴趣不大的话,可以直接跳过1、2节一大堆无聊的测试,直接看总结,甚至跳过此文也未尝不可。
  1、  抛砖(List的关键属性Capacity)
  前言已经说明是温故List,所以List是什么我也不打算花篇幅再次介绍(更何况一点点的介绍其扩容机制、迭代器和一些内部方法篇幅也会比较大,那就成了介绍List本身了,个人也不见得能总结得多好),这里我温故的就是List的扩容机制。
  用反编译工具看过List<T>源码的同学,应该都知道List对象在创建的时候其私有变量_defaultCapacity的值是4。
  (此截图来源与ILspy打开的List<T>源码部分,以下关于List<T>的源码部分均是如此)
  这个地方让我很长一段时间以为List对象在创建的时候就已经为它内部的数组对象,分配了4个元素,以后调用Add方法就以Capacity*2的线性速度增长,可事实狠狠的打了我一把脸,且看下面这段代码及其运行结果:
  回过头来再看源码,发现Capacity属性返回的是内部数组元素的长度,所以刚开始初始化的时候并没有被私有变量初始化,而且就算初始化的时候想给数组4个元素,也不知道List<T>对象中T的实例(T为引用类型的情况下),该分配多少内存CLR是不知道的。
  所以我猜测_defaultCapacity赋值给Capacity是在List首次调用Add方法,或者带元素初始化的时候,果不其然,看下面这段代码:
  找到源码验证一下:
  那么扩容的机制也就理所当然的被我们找到了,就是EnsureCapacity方法,this._szie是当前List元素个数,this._items是内部数组根据Capacity扩容后的数组长度,扩容触发的条件是this._size+1>this._items.Length, 总结说来就是,Capacity的初始值是0,在不主动改变Capacity的情况下使用Add方法会初始化为4,当List元素达到4且继续调用Add的话Capacity就会乘以2变成8,然后如此反复,直到达到2146435071(应该是2^31-1)为止。
  2、  引玉(主动操作Capacity是否可带来性能的提升)
  通过第1节的分析,想要Add到1亿级别的数量,需要扩容27次,2^27破亿(Capacity从0到4一次,从4到2^27花26次)
  那么,正常初始化1亿Int32类型数据需要耗时多久呢?通过下面这段代码可以发现是681ms。
  假如我把Capacity初始值设置为1亿,省去那26次扩容带来的时间损耗呢?
  为了避免电脑CPU时间段的差异对结果造成影响,我分别运行了3次,不初始化Capacity的情况下耗时700ms左右,初始化Capacity的情况下耗时450ms左右,这么说来是可以带来一定性能的提升的。那么把Capacity设置为3千万,让它扩容两次破亿:
  这么一看减少扩容次数是可以带来性能上的提升的。但是这里List集合是值类型的集合,换成引用类型又当如何呢?看如下代码:
  减少扩容次数后:
  结论依旧是可以带来性能上的提升的。但是个人觉得实际应用中通过设置Capacity去提升性能是不可取的,一方面是使用了对性能的提升并没有显著的改善,况且上亿级别的数据需要缓存的话,一般会使用专门的缓存服务器,另一方面是数据量不好确认,Capacity的设置很难合理,而且使用List的初衷,本来就是为了方便。而且看下面这段代码,我把Test的实例对象放进循环里(实际应用中这也更符合测试思路,毕竟上面的测试List存的都是同一个实例对象),还不等你提升性能,CLR托管内存就爆了。
  3、  总结(知新)
  对这次List的重温,更全面的了解其扩容机制,此间带来的收获主要在以下几点:
  (1)Capacity的设置确实能为List的初始化带来性能的提升,但是一般情况下不使用这种方式。
  (2)List扩容的这种机制其实很巧妙,在自定义集合中有值得借鉴的地方。
  (3)意识到托管内存不宜缓存大量数据,同时引导我再次了解GC处理机制。
  (4)List内部变量在方法体的使用中,使用了大量的lock规避引用冲突,这种严谨在多线程、iis线程池等引用的过程中,也是非常值得借鉴的。
  (5)为以后学习其它类似特性的对象提供了思路,假如我现在需要学习一个新的组件或框架,我首先会寻找它的API文档和使用说明、动手测试、大胆猜想、借助源码以及再测试来求证……

上文内容不用于商业目的,如涉及知识产权问题,请权利人联系博为峰小编(021-64471599-8017),我们将立即处理。
《2023软件测试行业现状调查报告》独家发布~

关注51Testing

联系我们

快捷面板 站点地图 联系我们 广告服务 关于我们 站长统计 发展历程

法律顾问:上海兰迪律师事务所 项棋律师
版权所有 上海博为峰软件技术股份有限公司 Copyright©51testing.com 2003-2024
投诉及意见反馈:webmaster@51testing.com; 业务联系:service@51testing.com 021-64471599-8017

沪ICP备05003035号

沪公网安备 31010102002173号