List<>是c#中很常見的一種集合形式,近期在閱讀c#源碼時,發現了一個頗有意思的定義:c#
[DebuggerTypeProxy(typeof(Mscorlib_CollectionDebugView<>))] [DebuggerDisplay("Count = {Count}")] [Serializable] 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]; // 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; } } ... ... ... private void EnsureCapacity(int min) { if (_items.Length < min) { int newCapacity = _items.Length == 0? _defaultCapacity : _items.Length * 2; if ((uint)newCapacity > Array.MaxArrayLength) newCapacity = Array.MaxArrayLength; if (newCapacity < min) newCapacity = min; Capacity = newCapacity; } }
咦,_defaultCapacity = 4, _items.Length * 2。抱着懷疑的態度,有了如下這一篇文章。性能
帶着懷疑的態度,咱們新建一個Console程序,Debug一下。測試
var list = new List<int>(); Console.WriteLine(list.Capacity);
運行結果:
圖片:ui
...怎麼是0呢?必定是我打開的姿式不對,再看一下源碼。發現:google
static readonly T[] _emptyArray = new T[0]; ... ... public List() { _items = _emptyArray; }
哦,這就對了,初始化時候固然是0。那這個_defaultCapacity有何用?繼續看源碼。code
if (_items.Length < min) { int newCapacity = _items.Length == 0? _defaultCapacity : _items.Length * 2;
發現這個三元表達式,爲何要這樣作呢?翻了一下google,發現了這樣一段文字:對象
List實例化一個List對象時,Framework只是在內存中申請了一塊內存存放List對象自己,系統此時並不知道List會有多少個item元素及元素自己大小。當List添加了第一個item時,List會申請能存儲4個item元素的存儲空間,此時Capacity是4,當咱們添加第五個item時,此時的Capacity就會變成8。也就是當List發現元素的總數大於Capacity數量時,會主動申請且從新分配內存,每次申請的內存數量是以前item數量的兩倍。而後將以前全部的item元素複製到新內存。blog
上面的測試,Capacity=0已經證實了上述這段話的圖片
List實例化一個List對象時,Framework只是在內存中申請了一塊內存存放List對象自己,系統此時並不知道List會有多少個item元素及元素自己大小。ip
接下來咱們證實
當List添加了第一個item時,List會申請能存儲4個item元素的存儲空間,此時Capacity是4
圖片:
RT,接下來,咱們證實
咱們添加第五個item時,此時的Capacity就會變成8。
圖片:
RT,的確是這樣。
那是否咱們得出一個結論,由於不定長的List在Add的時候,頻繁的從新申請、分配內存、複製到新內存,效率是否還能夠再提高一下呢?
咱們先試一下
for (int i = 0; i < count; i++) { var listA = new List<int>(10); listA.Add(i); }
循環次數 | 定長長度 | 運行時間 |
---|---|---|
100 | 0 | 144 |
100 | 5 | 23 |
100 | 6 | 49 |
100 | 7 | 45 |
100 | 8 | 73 |
100 | 9 | 21 |
100 | 10 | 22 |
運行結果:註定長爲0表示未設置List長度
循環次數 | 定長長度 | 運行時間 |
---|---|---|
10000 | 0 | 3741 |
10000 | 5 | 3934 |
10000 | 6 | 4258 |
10000 | 7 | 4013 |
10000 | 8 | 4830 |
10000 | 9 | 4159 |
10000 | 10 | 2370 |
好吃鯨...爲啥9和10差距這麼多。。。
咱們加大循環次數。結果:
循環次數 | 定長長度 | 運行時間 |
---|---|---|
1000000 | 0 | 317590 |
1000000 | 5 | 263378 |
1000000 | 6 | 150444 |
1000000 | 7 | 157317 |
1000000 | 8 | 139041 |
1000000 | 9 | 124714 |
1000000 | 10 | 120547 |
隨着循環次數、定長的增長,能夠看出,頻繁的從新申請、分配內存、複製到新內存,是很耗費時間和性能的。 在之後的工做中,若是有頻繁的List.Add,特別是循環Add,不妨考慮一下給List設置一個定長。