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要执行的内容。