距離上一篇博文,差不多兩年了。終於憋出來了一篇。[手動滑稽]
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設置一個定長。