距離上一篇博文,差不多兩年了。終于憋出來了一篇。[手動滑稽]
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 | 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/
本文版權歸作者和部落格園共有,歡迎轉載,但未經作者同意必須保留此段聲明,且在文章頁面明顯位置給出原文連接配接,否則保留追究法律責任的權利。