tsx81428 发表于 9 小时前

【C#知识点详解】List<T>储存布局详解

        本日来先容一下List内部的存储布局,话不多说直接开始。

内部数据

        List内部接纳了连续数组的方式存储数据,此中包罗了三个紧张的成员变量,示比方下:
// 用于存储数据的数组
internal T[] _items;
// 列表中实际包含数据的数量
internal int _size;
// 版本号,用于迭代时检测是否修改
internal int _version;

[*]T[] _items:存储数据的数组,实际存储数据的地方。
[*]int _size:实际包罗数据的数量,用于记载_items实际存储数据的数量。
[*]int _version:版本号,List迭代时用于检测_items是否被修改。
        
数据利用

        添加数据

        List添加数据是在数据队列尾部举行添加(不是数组尾部),添加时会对_size计数举行增长,示例代码如下:
public void Add(T item)
{
    _version++;
    T[] array = _items;
    int size = _size;
    if ((uint)size < (uint)array.Length)
    {
      _size = size + 1;
      array = item;
    }
}         当_items容积不敷时,List会举行扩容,默认会扩容到原来的2倍。然后会创建一个新的newItems数组,并将原先的_items数据拷贝进去,示例代码如下:
internal void Grow(int capacity)
{
    int newCapacity = _items.Length == 0 ? DefaultCapacity : 2 * _items.Length;

    if ((uint)newCapacity > Array.MaxLength)
                newCapacity = Array.MaxLength;

    if (newCapacity < capacity)
                newCapacity = capacity;

    Capacity = newCapacity;
}

public int Capacity
{
    get => _items.Length;
    set
    {
      if (value != _items.Length)
      {
            if (value > 0)
            {
                T[] newItems = new T;
                if (_size > 0)
                {
                  Array.Copy(_items, newItems, _size);
                }
                _items = newItems;
            }
      }
    }
}         
        移除数据

        List在移除数据的时间较常用的两个方法是Remove(T item)和RemoveAt(int index)。Remove()的实行过程也是先找到数据所在位置的索引,然后在调用RemoveAt()方法,示例代码如下:
public bool Remove(T item)
{
    int index = IndexOf(item);
    if (index >= 0)
    {
      RemoveAt(index);
      return true;
    }

    return false;
}         RemoveAt()的实行过程是将索引后的数据拷贝前移,拷贝完成后再将末了一位设置成默认值,示例代码如下:
public void RemoveAt(int index)
{
    _size--;
    if (index < _size)
    {
      Array.Copy(_items, index + 1, _items, index, _size - index);
    }
    if (RuntimeHelpers.IsReferenceOrContainsReferences<T>())
    {
      _items = default!;
    }
    _version++;
}         
        插入数据

        List在插入数据时会调用Insert(int index, T item)接口,插入数据的实行过程与移除数据比力相似,插入数据是将索引位置的数据拷贝后移,然后将索引位置举行赋值,示例代码如下:
public void Insert(int index, T item)
{
    // Note that insertions at the end are legal.
    if ((uint)index > (uint)_size)
    {
      ThrowHelper.ThrowArgumentOutOfRangeException(ExceptionArgument.index, ExceptionResource.ArgumentOutOfRange_ListInsert);
    }
    if (_size == _items.Length) Grow(_size + 1);
    if (index < _size)
    {
      Array.Copy(_items, index, _items, index + 1, _size - index);
    }
    _items = item;
    _size++;
    _version++;
}         
        访问数据

        在List中访问数据最常用的方式是索引访问,通过this直接获取修改数据。由于数据存储在_items数组中,访问数据时唯一必要留意的只有越界题目。示例代码如下:
public T this
{
    get
    {
      // Following trick can reduce the range check by one
      if ((uint)index >= (uint)_size)
      {
            ThrowHelper.ThrowArgumentOutOfRange_IndexMustBeLessException();
      }
      return _items;
    }

    set
    {
      if ((uint)index >= (uint)_size)
      {
            ThrowHelper.ThrowArgumentOutOfRange_IndexMustBeLessException();
      }
      _items = value;
      _version++;
    }
}
利用场景

        基于前面临List的先容,List的利用场景可以归纳如下:


[*]List提供了机动的数组存储空间,当数组元素数量不确定时可以选择List。
[*]List有较为机动的数据访问机制,通过索引值即可访问数据,在必要随机访问数据时可以选择List。
[*]List存储的数据为连续的数组空间,在遍历数据时有较快的速率。
[*]List存储的数据为连续的数组空间,在频仍的添加/移除数据时,发起利用Add(T item)和RemoveAt(int lastIndex)方法,这两个方法是在数组尾部举行的数据利用。
[*]List存储的数据为连续的数组空间,在调用Insert(int index, T item)和RemoveAt(int index)方法插入/移除数据时,会举行数组数据的拷贝,以是不发起高频利用。必要高频在头部/中心插入/删除数据时可以利用LinkedList<T>。

相干文档链接

List官方文档:https://learn.microsoft.com/zh-cn/dotnet/api/system.collections.generic.list-1?view=net-9.0
List源码地点:
https://source.dot.net/#System.Private.CoreLib/src/libraries/System.Private.CoreLib/src/System/Collections/Generic/List.cs

免责声明:如果侵犯了您的权益,请联系站长,我们会及时删除侵权内容,谢谢合作!更多信息从访问主页:qidao123.com:ToB企服之家,中国第一个企服评测及商务社交产业平台。
页: [1]
查看完整版本: 【C#知识点详解】List<T>储存布局详解