天天看點

深入探讨List<>中的一個姿勢。

距離上一篇博文,差不多兩年了。終于憋出來了一篇。[手動滑稽]

List<>是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);
           

運作結果:

圖檔:

深入探讨List&lt;&gt;中的一個姿勢。

...怎麼是0呢?一定是我打開的姿勢不對,再看一下源碼。發現:

static readonly T[]  _emptyArray = new T[0];
...
...
public List() {
     _items = _emptyArray;
}
           

哦,這就對了,初始化時候當然是0。那這個_defaultCapacity有何用?繼續看源碼。

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元素複制到新記憶體。

上面的測試,Capacity=0已經證明了上述這段話的

List執行個體化一個List對象時,Framework隻是在記憶體中申請了一塊記憶體存放List對象本身,系統此時并不知道List會有多少個item元素及元素本身大小。

接下來我們證明

當List添加了第一個item時,List會申請能存儲4個item元素的存儲空間,此時Capacity是4
深入探讨List&lt;&gt;中的一個姿勢。

RT,接下來,我們證明

我們添加第五個item時,此時的Capacity就會變成8。
深入探讨List&lt;&gt;中的一個姿勢。

RT,的确是這樣。

那是否我們得出一個結論,因為不定長的List在Add的時候,頻繁的重新申請、配置設定記憶體、複制到新記憶體,效率是否還可以再提升一下呢?

我們先試一下

for (int i = 0; i < count; i++)
{
    var listA = new List<int>(10);
    listA.Add(i);
}
           
循環次數 定長長度 運作時間
100 144
5 23
6 49
7 45
8 73
9 21
10 22

運作結果:注定長為0表示未設定List長度

10000 3741
3934
4258
4013
4830
4159
2370

好吃鲸...為啥9和10差距這麼多。。。

我們加大循環次數。結果:

1000000 317590
263378
150444
157317
139041
124714
120547

随着循環次數、定長的增加,可以看出,頻繁的重新申請、配置設定記憶體、複制到新記憶體,是很耗費時間和性能的。

在以後的工作中,如果有頻繁的List.Add,特别是循環Add,不妨考慮一下給List設定一個定長。

鳥文名:YamatAmain

地 址:http://www.cnblogs.com/YamatAmain/

本文版權歸作者和部落格園共有,歡迎轉載,但未經作者同意必須保留此段聲明,且在文章頁面明顯位置給出原文連接配接,否則保留追究法律責任的權利。

繼續閱讀