数据结构-List

List是C#中一个常见的可伸缩的数组组件,通常用来代替数组,其底层的数据结构就是基于数组(非链表).由于它是可伸缩的,所有我们在编写程序的时候不需要手动的去分配数组的大小,甚至将它当作一个链表去使用。
1.构造函数

public class List<T> : IList<T>, System.Collections.IList, IReadOnlyList<T>
{
    private const int _defaultCapacity = 4;

    private T[] _items; // 主要数组
    private int _size; // 数组大小(非容量)
    private int _version; // List的版本(List内部维持)

    static readonly T[] _emptyArray = new T[0]; // 默认空数组

    // 构造函数: 默认为空数组,容量为0
    public List()
    {
        _items = _emptyArray;
    }

    // 构造函数:声明初始容量
    public List(int capacity)
    {
        if (capacity < 0) ThrowHelper.ThrowArgumentOutOfRangeException(ExceptionArgument.capacity, ExceptionResource.ArgumentOutOfRange_NeedNonNegNum);
        Contract.EndContractBlock();

        if (capacity == 0)
            _items = _emptyArray;
        else
            _items = new T[capacity];
    }

    // 构造函数:声明集合的内容,列表的大小和容量都等于给定的集合 
    public List(IEnumerable<T> collection)
    {
        if (collection == null)
            ThrowHelper.ThrowArgumentNullException(ExceptionArgument.collection);
        Contract.EndContractBlock();

        ICollection<T> c = collection as ICollection<T>;
        if (c != null)
        {
            int count = c.Count;
            if (count == 0)
            {
                _items = _emptyArray;
            }
            else
            {
                _items = new T[count];
                c.CopyTo(_items, 0);
                _size = count;
            }
        }
        else
        {
            _size = 0;
            _items = _emptyArray;
            using (IEnumerator<T> en = collection.GetEnumerator())
            {
                while (en.MoveNext())
                {
                    Add(en.Current);
                }
            }
        }
    }
}

List的内部是通过使用数组实现的,而非链表,并且没有给与指定容量,初始的容量是0。
注意:_version 的作用主要是在ForEach 和 Enumerator中使用,主要目的是防止在list遍历时或者执行操作时,对集合内的元素修改。如果此时版本号不一致,则会提示错误。InvalidOperationException:集合已被修改;枚举操作可能无法执行。
2.List的Capacity

public int Capacity
    {
        get => this._items.Length;
        set
        {
            if (value < this._size)
                ThrowHelper.ThrowArgumentOutOfRangeException(ExceptionArgument.value, ExceptionResource.ArgumentOutOfRange_SmallCapacity);
            if (value == this._items.Length)
                return;
            if (value > 0)
            {
                T[] destinationArray = new T[value];
                if (this._size > 0)
                    // 将原数组的数据拷贝到新数组中
                    Array.Copy((Array)this._items, (Array)destinationArray, this._size);
                this._items = destinationArray;
            }
            else
                this._items = List<T>._emptyArray;
        }
    }

capacity是list的容量,当list容量不足时,就会动态的扩容一倍的大小
3.Add接口剖析

public void Add(T item)
    {
        ++this._version;
        T[] items = this._items;
        int size = this._size;
        if ((uint)size < (uint)items.Length)
        {
            this._size = size + 1;
            items[size] = item;
        }
        else
            this.AddWithResize(item);
    }

    private void AddWithResize(T item)
    {
        int size = this._size;
        this.Grow(size + 1);
        this._size = size + 1;
        this._items[size] = item;
    }

    internal void Grow(int capacity)
    {
        int num = this._items.Length == 0 ? _defaultCapacity : 2 * this._items.Length;
        if ((uint)num > 2147483591U)
            num = 2147483591;
        if (num < capacity)
            num = capacity;
        this.Capacity = num;
    }

List每次添加一个元素时,都会判断数组的容量够不够,如果不够将调用Grow函数来增加容量.
其中Grow 函数有这么一行代码
int num = this._items.Length = 0 ? _defaultCapacity : 2 * this._items.Length;
每次容量不够时,整个数组的容量都会扩增一倍, _defaultCapacity表示容量的默认值为4,因此扩充的路线为4, 8, 16, 32, 64, 128, 256, 512 …以此类推。
List使用数组形式作为底层数据结构,优点是使用索引方式获取元素很快。缺点是扩容时会很糟糕,每次针对数组进行new操作都会造成内存垃圾,这会给垃圾回收(GC)带来很大负担。
注意: 这里以2的倍数扩容的方式可以为GC减轻负担,但是如果数组被连续申请扩容,还是会造成GC的不小负担,特别是代码中的List频繁使用Add时。此外如果数量使用不当,会浪费大量内存空间,例如当元素的数量为520时,List会被扩容到1024个元素,如果不使用剩余的504个空间单位,就会造成大部分内存空间的浪费。
4.Remove接口剖析

//删除给定索引处的元素。列表的大小减1
public bool Remove(T item)
{
    int index = IndexOf(item);
    if(index >= 0)
    {
        RemoveAt(index);
        return true;
    }
 
    return false;
}
//返回此列表范围内给定值首次出现的索引
//该列表从头到尾向前搜索
//使用Object.Equals方法将列表中的元素与给定值进行比较
//此方法使用Array.IndexOf方法进行搜索
public int IndexOf(T item)
{
    Contract.Ensures(Contract.Result<int>() >= -1)
    Contract.Ensures(Contract.Result<int>() < Count)
    return Array.IndexOf(_items, item, 0, _size);
}
 
//删除给定索引处的元素,列表的大小减1
public void RemoveAt(int index)
{
    if((uint)index >= (uint)_size)
    {
        ThrowHelper.ThrowArgumentOutOfRangeException();
    }
    Contract.EndContractBlock();
    _size--;
    if(index < _size)
    {
        Array.Copy(_items, index + 1, _items, index, _size - index);
    }
    _items[_size] = default(T);
    _version++;
}

Remove函数中包含IndexOf和RemoveAt函数,其中使用IndexOf函数是为了找到元素的索引位置,使用RemoveAt可以删除指定位置的元素。
从源码中可以看到,元素删除的原理就是使用Array.Copy对数组进行覆盖。IndexOf是用Array.IndexOf接口来查找元素的索引位置,这个接口本身的内部实现就是按索引顺序从0到n对每个位置进行比较。
5.Insert接口剖析

public void Insert(int index, T item)
    {
        if ((uint)index > (uint) this._size)
            ThrowHelper.ThrowArgumentOutOfRangeException(ExceptionArgument.index, ExceptionResource.ArgumentOutOfRange_ListInsert);
        if (this._size == this._items.Length)
            this.Grow(this._size + 1);
        if (index < this._size)
            Array.Copy((Array)this._items, index, (Array)this._items, index + 1, this._size - index);
        this._items[index] = item;
        ++this._size;
        ++this._version;
    }

与Add接口一样,先检查数组容量是否足够,不足则扩容两倍。
Insert()插入元素时,采用的与Remove类似,是复制数组的形式,将数组里指定元素后面的所有元素向后移动一个位置。

6.其他接口剖析
- []接口
public T this[int index]
{
get
{
if ((uint) index> (uint) _size)
ThrowHelper.ThrowArgumentOutOfRange_IndexMustBeLessException();
return _items[index];
}
set
{
if ((uint) index > (uint) _size)
ThrowHelper.ThrowArgumentOutOfRange_IndexMustBeLessException();
_items[index] = value;
_version++;
}
}
直接使用数组的索引方式获取元素
- clear接口

  • public void Clear()
    {
    ++this._version;
    if (RuntimeHelpers.IsReferenceOrContainsReferences())
    {
    int size = this._size;
    this._size = 0;
    if (size < 0)
    return;
    Array.Clear(this._items, 0, _size);
    }
    else
    this._size = 0;
    }`
    采用了Array.Clear()清除对象引用的标记,便于垃圾回收。
  • Contains接口
    public bool Contains(T item) => this._size != 0 && this.IndexOf(item) > 0;
    直接使用Array.IndexOf查找元素是否存在。
    - ToArray接口
- ` public T[] ToArray()
    {
        if (this._size == 0)
            return List<T>._emptyArray;
        T[] destinationArray = new T[this._size];
        Array.Copy((Array) this._items, (Array)destinationArray, this._size);
        return destinationArray;
    }`

ToArray接口是重新创建了一个大小一直的数组,然后将原有数组上的内容复制到新数组中,在将新数组返回。
注意:如果使用过多,就会造成大量内存的分配,会使内存上留上很多无用的垃圾。
- Find接口

 public T? Find(Predicate<T> match)
    {
        if (match == null)
            ThrowHelper.ThrowArgumentNullException(ExceptionArgument.match);
        for (int index = 0; index < this._size; index++)
        {
            if (match(this._items[index]))
                return this._items[index];
        }

        return default(T);
    }

查找接口,线性查找,对每个元素进行比较,时间复制度为O(n)
- Enumerator接口

public List<T>.Enumerator GetEnumerator() => new Enumerator(this);

    public struct  Enumerator : IEnumerator<T>, IDisposable, IEnumerator
    {
        private readonly List<T> _list;
        private int _index;
        private readonly int _version;
        private T _current;

        internal Enumerator(List<T> list)
        {
            this._list = list;
            this._index = 0;
            this._version = list._version;
            this._current = default(T);
        }

        public void Dispose()
        {

        }

        public bool MoveNext()
        {
            List<T> list = this._list;
            // _version判断是否对list有修改
            if (this._version != list._version || (uint)this._index >= (uint)list._size)
            {
                return this.MoveNextRare();
            }

            this._current = list._items[this._index];
            this._index++;
            return true;
        }

        private bool MoveNextRare()
        {
            // _version版本不一致,则抛出异常
            if (this._version != this._list._version)
                ThrowHelper.ThrowInvalidOperationException_InvalidOperation_EnumFailedVersion();
            this._index = this._list._size + 1;
            this._current = default(T);
            return false;
        }

        public T Current => this._current;

        object? IEnumerator.Cureent
        {
            get
            {
                if (this._index == 0 || this._index == this._list._size + 1)
                    ThrowHelper.ThrowInvalidOperationException_InvalidOperation_EnumOpCantHappen();
                return (object)this.Current;
            }
        }

        void IEnumerator.Reset()
        {
            if (this._version != this._list._version)
                ThrowHelper.ThrowInvalidOperationException_InvalidOperation_EnumFailedVersion();
            this._index = 0;
            this._current = default(T);
        }
    }

Enumerator接口是枚举迭代部分细节的接口,注意:每次获得迭代器时,Enumerator都会被创建出来,如果大量使用迭代器,则会产生大量的垃圾。
- Sort接口

 public void Sort(int index, int count, IComparable<T> comparer)
    {
        if (index < 0)
            ThrowHelper.ThrowIndexArgumentOutOfRange_NeedNonNegNumException();
        if (count < 0)
            ThrowHelper.ThrowArgumentOutOfRangeException(ExceptionArgument.count, ExceptionResource.ArgumentOutOfRange_NeedNonNegNum);
        if (this._size - index < count)
            ThrowHelper.ThrowArgumentException(ExceptionResource.Argument_InvalidOffLen);
        if (count > 1)
            Array.Sort<T>(this._items, index, count, comparer);
        ++this._version;
    }

Sort接口是排序接口,它使用了Array.Sort接口进行排序。
- ForEach

public void ForEach(Action<T> action)
{
  if (action == null)
    ThrowHelper.ThrowArgumentNullException(ExceptionArgument.match);
  int version = this._version;
  for (int index = 0; index < this._size && (version == this._version || !BinaryCompatibility.TargetsAtLeast_Desktop_V4_5); ++index)
    action(this._items[index]);
  if (version == this._version || !BinaryCompatibility.TargetsAtLeast_Desktop_V4_5)
    return;
  ThrowHelper.ThrowInvalidOperationException(ExceptionResource.InvalidOperation_EnumFailedVersion);
}

对List中的每个元素执行指定操作,具体操作时Action要执行的内容。

评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

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

抵扣说明:

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

余额充值