C# List源码分析(一)
来源:互联网 发布:圆形扇子淘宝网 编辑:程序博客网 时间:2024/06/04 18:53
事件原因,之前在公司写代码的时候,带我的师傅建议我对List的长度最好在初始化的时候进行优化,这样对GC更加友好,所以就有了这个文章,来理解下List 容量自适应的实现。
List 继承于IList,IReadOnlyList
// C# 源码public class List<T> : IList<T>, System.Collections.IList, IReadOnlyList<T>{ private const int _defaultCapacity = 4; private T[] _items; [ContractPublicPropertyName("Count")] private int _size; private int _version; [NonSerialized] private Object _syncRoot; static readonly T[] _emptyArray = new T[0]; // 其他内容 }
继承层次上跟JAVA差不多,继承于IList,然后在网上是ICollection
默认容量
从代码中可以看出,默认的容量是4,但是根JAVA一样,都不会立刻申请内存空间
// JAVA code /** * Constructs an empty list with an initial capacity of ten. */ public ArrayList() { // 该变量初始化为 // private static final Object[] DEFAULTCAPACITY_EMPTY_ELEMENTDATA = {}; this.elementData = DEFAULTCAPACITY_EMPTY_ELEMENTDATA; }// C# code // Constructs a List. The list is initially empty and has a capacity // of zero. Upon adding the first element to the list the capacity is // increased to 16, and then increased in multiples of two as required. public List() { _items = _emptyArray; }
但是很有意思,微软给出的官方的注释说容量会在第一次增加到16,然后每次都是加倍增加的。
但是看了代码,我觉得微软的这个注释可能需要更新了,这初始化的长度很明显是4啊,还写了注释告诉我是16。
[TestClass]public class BasicTest{ [TestMethod] public void TestMethod1() { List<int> list = new List<int>(); Assert.AreEqual(list.Capacity, 0); list.Add(1); Assert.AreEqual(list.Capacity, 4); }}
写了个简单的UT,没错,初始容量为0,第一次初始化容量为4.说明了代码确实比注释容易维护。
自增长实现方式
Java跟C#有的地方确实不太一样,C#的Add操作返回void,Java返回boolean,其实我觉得C#挺好的,至少Java我写add还真的从来没用过他的返回值。看了源码,我也没发现这个add操作什么情况下返回false。
参考微软提供的C# Add的代码。
// C# Code// Adds the given object to the end of this list. The size of the list is// increased by one. If required, the capacity of the list is doubled// before adding the new element.//public void Add(T item) { if (_size == _items.Length) EnsureCapacity(_size + 1); _items[_size++] = item; _version++;}// Ensures that the capacity of this list is at least the given minimum// value. If the currect capacity of the list is less than min, the// capacity is increased to twice the current capacity or to min,// whichever is larger.private void EnsureCapacity(int min) { if (_items.Length < min) { int newCapacity = _items.Length == 0? _defaultCapacity : _items.Length * 2; // Allow the list to grow to maximum possible capacity (~2G elements) before encountering overflow. // Note that this check works even when _items.Length overflowed thanks to the (uint) cast if ((uint)newCapacity > Array.MaxArrayLength) newCapacity = Array.MaxArrayLength; if (newCapacity < min) newCapacity = min; Capacity = newCapacity; }}// Gets and sets the capacity of this list. The capacity is the size of// the internal array used to hold items. When set, the internal // array of the list is reallocated to the given capacity.// public int Capacity { get { Contract.Ensures(Contract.Result<int>() >= 0); return _items.Length; } set { if (value < _size) { ThrowHelper.ThrowArgumentOutOfRangeException( ExceptionArgument.value, ExceptionResource.ArgumentOutOfRange_SmallCapacity); } Contract.EndContractBlock(); if (value != _items.Length) { if (value > 0) { T[] newItems = new T[value]; if (_size > 0) { Array.Copy(_items, 0, newItems, 0, _size); } _items = newItems; } else { _items = _emptyArray; } } }}
最初所有数据中推荐编程者使用List的一大重要原因,就是自适应。在Add执行之前一定会执行EnsureCapacity函数,来确保数组中的长度足够,可以添加新的元素进去。
从源码上看得出来,代码是线程不安全的。所以最好别使用公用的List来添加,并发情况下,无法判断_size++的执行顺序。
C# 一大特性感觉不太容易让人理解啊,就是这个属性,《CLR via C#》一书中推荐取消Property,我觉得还是有点道理的,跟field还真是傻傻分不清楚,但是本质上是一个方法。
可以看出,在配置Capacity属性的时候,会对_items进行重新new,然后复制,是一个频繁操作内存的操作。
对GC友好就是指这里吧,如果在初始化的时候配置了容量的话,内部存储的时候就不会使用Array.Copy方法,从而产生很多垃圾对象。
C# 中 List默认的容量其实是4,所以最好还是初始化容量吧,可以想象,如果一个列表里面有129个元素,那么代码中对Capacity的调用会有很多次,4->8->16->32->64->128->256,不但最后的容量中产生了大量的浪费,前面的一堆对象也都需要GC搞定了。也就是252个对象。浪费还是很严重的。
List中的Remove
List的Remove操作每次都要进行内存的整理,其实是操作消耗较大的,代码如下:
// Removes the element at the given index. The size of the list is// decreased by one.public bool Remove(T item) { int index = IndexOf(item); if (index >= 0) { RemoveAt(index); return true; } return false;}// Returns the index of the first occurrence of a given value in a range of// this list. The list is searched forwards from beginning to end.// The elements of the list are compared to the given value using the// Object.Equals method.// // This method uses the Array.IndexOf method to perform the// search.// public int IndexOf(T item) { Contract.Ensures(Contract.Result<int>() >= -1); Contract.Ensures(Contract.Result<int>() < Count); return Array.IndexOf(_items, item, 0, _size);}// Array.cspublic static int IndexOf<T>(T[] array, T value, int startIndex, int count) { if (array==null) { throw new ArgumentNullException("array"); } if (startIndex < 0 || startIndex > array.Length ) { throw new ArgumentOutOfRangeException("startIndex", Environment.GetResourceString("ArgumentOutOfRange_Index")); } if (count < 0 || count > array.Length - startIndex) { throw new ArgumentOutOfRangeException("count", Environment.GetResourceString("ArgumentOutOfRange_Count")); } Contract.Ensures(Contract.Result<int>() < array.Length); Contract.EndContractBlock(); return EqualityComparer<T>.Default.IndexOf(array, value, startIndex, count);}// Removes the element at the given index. The size of the list is// decreased by one.public void RemoveAt(int index) { if ((uint)index >= (uint)_size) { ThrowHelper.ThrowArgumentOutOfRangeException(); } Contract.EndContractBlock(); _size--; if (index < _size) { Array.Copy(_items, index + 1, _items, index, _size - index); } _items[_size] = default(T); _version++;}
从代码来看,remove操作优先看的是能否找到该元素,如果能找到,将其移除,返回True,否则,返回false
C#的索引方法有点复杂,点到EqualityComparer里面看了一下索引的方法,这也是C#跟Java的不同之处了,Java的泛型里面是不能写入Primitive类型的,因为Primitive类型其实是不继承Object的,所以无法调用其中的equals方法。
但是C#是支持的,所以,会判断元类型的Type,然后选取对应的Equals方法。
现在回头看下RemoveAt方法,该方法仍然会调用Array.Copy操作,所以,可想而知删除操作的复杂度了,内存中平均删除一个元素,要移动n/2个元素,复杂度为O(n)
而RemoveAll方法本身是复杂度为O(n)的,所以最好不要在循环中写Remove操作吧。
以下为List源码地址
List源码
Array源码
- C# List源码分析(一)
- C# List源码分析(二)
- Java集合源码之路-List分析(一)
- Java集合源码之路-List分析(一)续
- xm list源码分析
- xm list源码分析
- STL源码分析--list
- xm list源码分析
- c# 源码深度剖析 list
- boost::mpl::list 源码分析
- <stl>list::sort源码分析
- List集合源码简单分析
- java.util.List源码分析
- List之Stack源码分析
- c# AntiSocialRobots 源码分析
- List源码解析之ArrayList源码分析
- List源码解析之Vector 源码分析
- List源码解析之LinkedList 源码分析
- Spring源码学习之一 ContextLoaderListener及XmlWebApplicationContext
- Hadoop参数设置中的关系疏理
- springmvc(十一)post乱码
- 高效使用Bitmaps, 神奇的Cache
- exit
- C# List源码分析(一)
- HttpWebRequest和HttpWebResponse类的认识
- spring自定义异常处理
- AS不能发布release版本的解决方案
- VC6.0 设置动态链接库工程生成dll以及lib文件的位置
- MongoDB的安装以及自启动问题
- 字符串HASH模板
- fir.im Weekly - 技术人也要苦练“七十二变”
- Android 模拟屏幕点击和物理按键方式