前言:關於學習方法的討論其實是個比較模糊的概念,對於List的介紹的資料其實已經很多了,但是一般是介紹List本身,我打算分享的是,以溫故List為例,來獲取新知識的這么一個過程。這里的新知識也不是什么新知識,依舊是算法、泛型、迭代器和GC回收機制等相關術語,只不過通過對List再分析,對這些東西的運用有了些新的認知,我要分享的就是再分析的這么一個過程,姑且允許我將其稱為學習方法吧,最起碼它是比較適合我的一種學習方法。另外,如果對List已經了解很深或者興趣不大的話,可以直接跳過1、2節一大堆無聊的測試,直接看總結,甚至跳過此文也未嘗不可。
1、 拋磚(List的關鍵屬性Capacity)
前言已經說明是溫故List,所以List是什么我也不打算花篇幅再次介紹(更何況一點點的介紹其擴容機制、迭代器和一些內部方法篇幅也會比較大,那就成了介紹List本身了,個人也不見得能總結得多好),這里我溫故的就是List的擴容機制。
用反編譯工具看過List<T>源碼的同學,應該都知道List對象在創建的時候其私有變量_defaultCapacity的值是4。
(此截圖來源與ILspy打開的List<T>源碼部分,以下關於List<T>的源碼部分均是如此)
這個地方讓我很長一段時間以為List對象在創建的時候就已經為它內部的數組對象,分配了4個元素,以后調用Add方法就以Capacity*2的線性速度增長,可事實狠狠的打了我一把臉,且看下面這段代碼及其運行結果:
回過頭來再看源碼,發現Capacity屬性返回的是內部數組元素的長度,所以剛開始初始化的時候並沒有被私有變量初始化,而且就算初始化的時候想給數組4個元素,也不知道List<T>對象中T的實例(T為引用類型的情況下),該分配多少內存CLR是不知道的。
所以我猜測_defaultCapacity賦值給Capacity是在List首次調用Add方法,或者帶元素初始化的時候,果不其然,看下面這段代碼:
找到源碼驗證一下:
那么擴容的機制也就理所當然的被我們找到了,就是EnsureCapacity方法,this._szie是當前List元素個數,this._items是內部數組根據Capacity擴容后的數組長度,擴容觸發的條件是this._size+1>this._items.Length, 總結說來就是,Capacity的初始值是0,在不主動改變Capacity的情況下使用Add方法會初始化為4,當List元素達到4且繼續調用Add的話Capacity就會乘以2變成8,然后如此反復,直到達到2146435071(應該是2^31-1)為止。
2、 引玉(主動操作Capacity是否可帶來性能的提升)
通過第1節的分析,想要Add到1億級別的數量,需要擴容27次,2^27破億(Capacity從0到4一次,從4到2^27花26次)
那么,正常初始化1億Int32類型數據需要耗時多久呢?通過下面這段代碼可以發現是681ms。
假如我把Capacity初始值設置為1億,省去那26次擴容帶來的時間損耗呢?
為了避免電腦CPU時間段的差異對結果造成影響,我分別運行了3次,不初始化Capacity的情況下耗時700ms左右,初始化Capacity的情況下耗時450ms左右,這么說來是可以帶來一定性能的提升的。那么把Capacity設置為3千萬,讓它擴容兩次破億:
這么一看減少擴容次數是可以帶來性能上的提升的。但是這里List集合是值類型的集合,換成引用類型又當如何呢?看如下代碼:
減少擴容次數后:
結論依舊是可以帶來性能上的提升的。但是個人覺得實際應用中通過設置Capacity去提升性能是不可取的,一方面是使用了對性能的提升並沒有顯著的改善,況且上億級別的數據需要緩存的話,一般會使用專門的緩存服務器,另一方面是數據量不好確認,Capacity的設置很難合理,而且使用List的初衷,本來就是為了方便。而且看下面這段代碼,我把Test的實例對象放進循環里(實際應用中這也更符合測試思路,畢竟上面的測試List存的都是同一個實例對象),還不等你提升性能,CLR托管內存就爆了。
3、 總結(知新)
對這次List的重溫,更全面的了解其擴容機制,此間帶來的收獲主要在以下幾點:
(1) Capacity的設置確實能為List的初始化帶來性能的提升,但是一般情況下不使用這種方式。
(2) List擴容的這種機制其實很巧妙,在自定義集合中有值得借鑒的地方。
(3) 意識到托管內存不宜緩存大量數據,同時引導我再次了解GC處理機制。
(4) List內部變量在方法體的使用中,使用了大量的lock規避引用沖突,這種嚴謹在多線程、iis線程池等引用的過程中,也是非常值得借鑒的。
(5) 為以后學習其它類似特性的對象提供了思路,假如我現在需要學習一個新的組件或框架,我首先會尋找它的API文檔和使用說明、動手測試、大膽猜想、借助源碼以及再測試來求證……。