深入探討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);

運行結果:
圖片:

...怎么是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
圖片:

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設置一個定長。


免責聲明!

本站轉載的文章為個人學習借鑒使用,本站對版權不負任何法律責任。如果侵犯了您的隱私權益,請聯系本站郵箱yoyou2525@163.com刪除。



 
粵ICP備18138465號   © 2018-2025 CODEPRJ.COM