前言:关于学习方法的讨论其实是个比较模糊的概念,对于 List 的介绍的资料其实已经很多了,但是一般是介绍 List 本身,我打算分享的是,以温故 List 为例,来获取新知识的这么一个过程。这里的新知识也不是什么新知识,依旧是算法、泛型、迭代器和 GC 回收机制等相关术语,只不过通过对 List 再分析,对这些东西的运用有了些新的认知,我要分享的就是再分析的这么一个过程,姑且允许我将其称为学习方法吧,最起码它是比较适合我的一种学习方法。另外,如果对 List 已经了解很深或者兴趣不大的话,可以直接跳过 1、2 节一大堆无聊的测试,直接看总结,甚至跳过此文也未尝不可。
1、 抛砖(List 的关键属性 Capacity)
前言已经说明是温故 List,所以 List 是什么我也不打算花篇幅再次介绍(更何况一点点的介绍其扩容机制、迭代器和一些内部方法篇幅也会比较大,那就成了介绍 List 本身了,个人也不见得能总结得多好),这里我温故的就是 List 的扩容机制。
用反编译工具看过 List<T> 源码的同学,应该都知道 List 对象在创建的时候其私有变量_defaultCapacity 的值是 4。
(此截图来源与 ILspy 打开的 List
这个地方让我很长一段时间以为 List 对象在创建的时候就已经为它内部的数组对象,分配了 4 个元素,以后调用 Add 方法就以 Capacity*2 的线性速度增长,可事实狠狠的打了我一把脸,且看下面这段代码及其运行结果:
回过头来再看源码,发现 Capacity 属性返回的是内部数组元素的长度,所以刚开始初始化的时候并没有被私有变量初始化,而且就算初始化的时候想给数组 4 个元素,也不知道 List
所以我猜测_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 文档和使用说明、动手测试、大胆猜想、借助源码以及再测试来求证……。
来源: https://www.cnblogs.com/BradPitt/p/8126860.html