C#数据结构与算法—学习笔记(二)List

Array

面对大量的数据需要存放,或者我们需要一次性遍历大量数据,我们会采取使用Array数组。Array数组是一个存储相同类型元素的固定大小的顺序集合。

Array的优点:在内存中连续存储,遍历对象方便,可以根据索引快速锁定对象。

Array的缺点:定长,在使用的时候长度是固定的,不能后期增长或缩短。

ArrayList

因为Array定长的原因,所以出现了ArrayList,这是一个动态数组,可以根据需要调整数组长度。ArrayList采用Object存储对象,所以它可以存储不同类型的对象。

ArrayList的优点:可以动态调整长度。

ArrayList的缺点:因为是用Object存对象,在存和使用值类型的对象时或发生装箱/拆箱的操作。个位数或几十个影响可能不大,但是我们使用数组就是为了方便管理大量对象,对象过多的时带来的性能影响会很大,并且可能会产生垃圾。

“万能”的List

所以我们需要一个能动态调整长度,并且不会发生装箱/拆箱的一个新的数据结构。List就应运而生了。

List是一个泛型集合,可以等效ArrayList,但是因为泛型的缘故,所以List不会产生装箱/拆箱的问题。List内部实现使用的是Array数组,并且也继承了IList接口。所以List在内存中的存储也是连续的,List也可以像ArrayList一样动态调整长度。

“万能”

List“万能”因为它能执行增删改查的所有操作,在删除元素后,还可以自动移动元素保证数组元素的连续性,能Foreach遍历,能Sort排序,能Contains判断是否包含元素。似乎List把一切对元素的操作都集成了。

会而不精

做一个不恰当的比喻:回家过年,有的人坐飞机回家,有的坐火车回家。飞机比火车块多了,那为什么还是很多人选择坐火车?飞机是快,但是飞机票贵。相比之下火车速度也不是很慢,票价也便宜。那对于List来说,它什么都能做,但是又都做不好。

每次插入或删除新的元素都需要重新移动一次元素,假设把第一个删除或者在第一个位置插入一个新的元素。那么List就需要遍历一遍数组把数组的每一个元素向前或者向后挪动一位。最坏的情况下List插入和删除的时间复杂度为O(n)。在插入和删除上效率不如LinkedList链表。

List查找元素是从头遍历,符合的就会返回,不符合继续向后找,直到结尾。最好的情况下,第一个元素就是我们要找的,但是最坏的情况下,不仅找到末尾才找到,List没有要查找的元素也会完整遍历一遍。查找的时间复杂度为O(n)。在查找的效率上没有Dictionary高。

List还有一个缺点,就是在它的Add方法上。下面是List里的一些字段。_defaultCapacity是List的默认长度。_items就是存放元素的数组。_size为元素的个数

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];        
            

 这是List的Add方法。在添加元素的时候会先进行一个判断,判断_size和_items的长度是否一样,一样说明数组已经存满了,需要进行扩容就会执行EnsureCapacity方法。

EnsureCapacity方法就是在计算扩容后的数组长度,如果在new List的时候没有传入大小,_items.Length=0,List会将数组扩容到默认大小,否则扩容到原来数组长度的2倍。

扩容计算后的长度会赋给Capacity 属性,在Set中很明显能看到重新new了一个新长度的数组,并把旧数组Copy过去。所以在内存中就是出现一个垃圾数组。假如在数组中存储了几百万的个元素,List正好扩容,一个有着几百万元素的垃圾数组就出现了,并且Copy几百万个元素也是个消耗性能的事情。

并且,

public void Add(T item) {
            if (_size == _items.Length) EnsureCapacity(_size + 1);
            _items[_size++] = item;
            _version++;
        }


private void EnsureCapacity(int min) {
            if (_items.Length < min) {
                int newCapacity = _items.Length == 0? _defaultCapacity : _items.Length * 2;
                // Allow the list to grow to maximum possible capacity (~2G elements) before encountering overflow.
                // Note that this check works even when _items.Length overflowed thanks to the (uint) cast
                if ((uint)newCapacity > Array.MaxArrayLength) newCapacity = Array.MaxArrayLength;
                if (newCapacity < min) newCapacity = min;
                Capacity = newCapacity;
            }
        }


public int Capacity {
            get {
                Contract.Ensures(Contract.Result<int>() >= 0);
                return _items.Length;
            }
            set {
                if (value < _size) {
                    ThrowHelper.ThrowArgumentOutOfRangeException(ExceptionArgument.value, ExceptionResource.ArgumentOutOfRange_SmallCapacity);
                }
                Contract.EndContractBlock();
 
                if (value != _items.Length) {
                    if (value > 0) {
                        T[] newItems = new T[value];
                        if (_size > 0) {
                            Array.Copy(_items, 0, newItems, 0, _size);
                        }
                        _items = newItems;
                    }
                    else {
                        _items = _emptyArray;
                    }
                }
            }
        }
            
   

学习数据结构的重要性就出现了,因为“万能”的List“学术不精”,所以要学会选择合适的数据结构。在我最开始的学习中,以为一个List可以打天下,随意使用,导致后面性能受到影响。

个人总结的注意事项:

在能确定长度的情况下选择Array,在不确定长度的情况下优先选择List。

在插入删除较少的环境下使用List,使用List可以自己先指定一个合适的大小。

.Net官方源码icon-default.png?t=N658https://referencesource.microsoft.com/#mscorlib/system/collections/generic/list.cs,9808f1f5ef16c436

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

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

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

抵扣说明:

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

余额充值