深刻探討List<>中的一個姿式。

距離上一篇博文,差很少兩年了。終於憋出來了一篇。[手動滑稽]

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。抱着懷疑的態度,有了如下這一篇文章。性能

defaultCapacity=4?

帶着懷疑的態度,咱們新建一個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設置一個定長。

相關文章
相關標籤/搜索