【Unity主程手记(摘录)】第一章(一) - List 底层源码剖析

第一章(一) - List 底层源码剖析

提示:个人学习总结,如有错误,敬请指正。



一、List底层

1.New - 构造

List内部是用数组实现,而非链表,并且没有指定指定capacity时,数组大小为0。


2.Add - 添加数据

        //将给定对象添加到列表末尾,列表大小+1
        //如果需要,在添加新元素前,列表容量增加一倍
        public void Add(T item) {
            if (_size == _items.Length) EnsureCapacity(_size + 1);
            _items[_size++] = item;
            _version++;
        }

每次添加新的元素都需要检查容量是否还够,不够就调用EnsureCapacity增加容量。

        //如果列表当前容量小于min,则容量将增加到当前容量的两倍或min,以较大者为准
        private void EnsureCapacity(int min) {
            if (_items.Length < min) {
                int newCapacity = _items.Length == 0? _defaultCapacity : _items.Length * 2;
                //在遇到溢出前,允许列表增长到最大可能的容量(大约2GB)
                //请注意,即使_items.Length由于Uint强制转换而溢出,此检查仍然有效
                if ((uint)newCapacity > Array.MaxArrayLength) newCapacity = Array.MaxArrayLength;
                if (newCapacity < min) newCapacity = min;
                Capacity = newCapacity;
            }
        }

在EnsureCapacity()中,每次容量不够,都会扩容一倍,_defaultCapacity表示容量的默认值为4,扩充容量默认为4->8->16->32…

总结:List使用数组形式作为底层数据结构,好处是使用索引方式提取元素很快,但在扩容的时候就会很糟糕,每次new数组都会造成内存垃圾,这给垃圾回收GC带来了很多负担。


3.Remove- 删除数据

Remove接口中包含了 IndexOf 和 RemoveAt,其中用 IndexOf 函数是位了找到元素的索引位置,用 RemoveAt 可以删除指定位置的元素。

简而言之,元素删除的原理其实就是用 Array.Copy 对数组进行覆盖。IndexOf 启用的是 Array.IndexOf 接口来查找元素的索引位置,这个接口本身内部实现是就是按索引顺序从0到n对每个位置的比较,复杂度为O(n)。


4.Insert- 插入数据

与Add接口一样,先检查容量是否足够,不足则扩容。从源码中获悉,Insert插入元素时,使用的用拷贝数组的形式,将数组里的指定元素后面的元素向后移动一个位置。

看到这里,可以我们明白了List的Add,Insert,IndexOf,Remove接口都是没有做过任何形式的优化,都使用的是顺序迭代的方式如果过于频繁使用的话,会导致效率降低,也会造成不少内存的冗余,使得垃圾回收(GC)时承担了更多的压力

其他接口也是一样比如AddRange、RemoveRange


5.其他方式

  • Sets or Gets:直接使用索引
  • Clear::调用时并不删除数组,而是将数组中的所有元素置为默认值(0或者null)然后设置_size为0,用于虚拟地表示当前容量。清零会将对象的引用标记从此集合中移除,从这个角度看,调用Clear清零是必要的。
  • Contains : 线性查找方式比较元素,对数组进行迭代,比较每个元素与参数的实例是否一致,如果一致则返回true,全部比较结束还没有找到,则认为查找失败
  • ToArray : 重新new了一个指定大小的数组,再将本身数组上的内容考别到新数组上,再返回出来。
  • Find: 和Contains一样也是线性查找
  • Enumerator: 注意每次获取迭代器时,Enumerator都会被创建(foreach的坑,.Net4.0后已经修复,但仍然不建议大量使用)
  • Sort: 快速排序,效率为O(nlgn)

6.总结

  • List 的效率并不高,只是通用性强而已,大部分的算法都使用的是线性复杂度的算法,这种线性算法当遇到规模比较大的计算量级时就会导致CPU的大量损耗。
  • List的内存分配方式也不合理,当其中元素不断增加会多次重新分配数组,抛弃原有数组。调用GC时候会造成压力。最好在List初始化的时候就声明Capacity
  • List不是线程安全的,并发情况下无法检查_size++的执行顺序,在多线程情况下使用List时应该注意安全,因此当我们在多线程间使用 List 时加上安全机制。
  • List并不高效,但是足够通用

附录

主程手记

  • 0
    点赞
  • 2
    收藏
    觉得还不错? 一键收藏
  • 0
    评论
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

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

抵扣说明:

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

余额充值