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

运行结果:
图片:325947-20171116141841624-2134579348.png

...怎么是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
图片:325947-20171116141859546-1303504693.jpg

RT,接下来,我们证明

我们添加第五个item时,此时的Capacity就会变成8。
图片:325947-20171116141910281-665860264.jpg

RT,的确是这样。
那是否我们得出一个结论,因为不定长的List在Add的时候,频繁的重新申请、分配内存、复制到新内存,效率是否还可以再提升一下呢?
我们先试一下

for (int i = 0; i < count; i++)
{
    var listA = new List<int>(10);
    listA.Add(i);
}
循环次数定长长度运行时间
1000144
100523
100649
100745
100873
100921
1001022

运行结果:注定长为0表示未设置List长度

循环次数定长长度运行时间
1000003741
1000053934
1000064258
1000074013
1000084830
1000094159
10000102370

好吃鲸...为啥9和10差距这么多。。。
我们加大循环次数。结果:

循环次数定长长度运行时间
10000000317590
10000005263378
10000006150444
10000007157317
10000008139041
10000009124714
100000010120547

随着循环次数、定长的增加,可以看出,频繁的重新申请、分配内存、复制到新内存,是很耗费时间和性能的。
在以后的工作中,如果有频繁的List.Add,特别是循环Add,不妨考虑一下给List设置一个定长。

转载于:https://www.cnblogs.com/YamatAmain/p/7844141.html

  • 0
    点赞
  • 0
    收藏
    觉得还不错? 一键收藏
  • 0
    评论

“相关推荐”对你有帮助么?

  • 非常没帮助
  • 没帮助
  • 一般
  • 有帮助
  • 非常有帮助
提交
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值