关于C#中List<T>类的使用建议

C#在为我们提供便捷的同时,也遮蔽了很多内部的实现,而研究这些实现能够帮助我们更好的优化程序。本篇主要简单介绍 List < T > 类在使用时的一些注意事项,List < T > 类内部是通过静态数组进行实现的,所以就有会有一下几点问题:

1. 添加元素时数组容量变化

每次添加(Add、Insert)元素时,需要重新评估数组的容量Capacity,如果当前数组的容量不足以容纳新元素,就会创建一个更大的数组,新数组大小 = 当前数组大小 * 2,然后将数组内容拷贝到新数组,此时旧的数组就会成为垃圾等待GC回收。

[__DynamicallyInvokable]
public int Capacity
{
    [__DynamicallyInvokable]
    get
    {
        return this._items.Length;
    }
    [__DynamicallyInvokable]
    set
    {
        if (value < this._size)
        {
            ThrowHelper.ThrowArgumentOutOfRangeException(ExceptionArgument.value, ExceptionResource.ArgumentOutOfRange_SmallCapacity);
        }
        if (value != this._items.Length)
        {
            if (value > 0)
            {
                T[] array = new T[value];
                if (this._size > 0)
                {
                    Array.Copy(this._items, 0, array, 0, this._size);
                }
                this._items = array;
                return;
            }
            this._items = List<T>._emptyArray;
        }
    }
}

//评估当前数组的大小,看看是否满足最小长度,如果不满足,则进行扩容
private void EnsureCapacity(int min)
{
    if (this._items.Length < min)
    {
        int num = (this._items.Length == 0) ? 4 : (this._items.Length * 2);
        if (num > 2146435071)
        {
            num = 2146435071;
        }
        if (num < min)
        {
            num = min;
        }
        this.Capacity = num;
    }
}

建议:
List< T >默认容量是4,为了避免GC,可以预测列表的大小规模,初始化列表时指定一个合理的容量。

2. 删除元素时进行拷贝

当进行元素删除时,内部会将被移除元素之后的所有元素进行前移,此时就会进行浅拷贝(删除最后一个元素不会进行拷贝,RemoveAll(Predicate< T > match)针对这一点做了优化)。以下是几个Remove函数源码。

public bool Remove(T item)
{
    int num = this.IndexOf(item);
    if (num >= 0)
    {
        this.RemoveAt(num);
        return true;
    }
    return false;
}

public void RemoveAt(int index)
{
    if (index >= this._size)
    {
        ThrowHelper.ThrowArgumentOutOfRangeException();
    }
    this._size--;
    if (index < this._size)
    {
        Array.Copy(this._items, index + 1, this._items, index, this._size - index);
    }
    this._items[this._size] = default(T);
    this._version++;
}

public void RemoveRange(int index, int count)
{
    if (index < 0)
    {
        ThrowHelper.ThrowArgumentOutOfRangeException(ExceptionArgument.index, ExceptionResource.ArgumentOutOfRange_NeedNonNegNum);
    }
    if (count < 0)
    {
        ThrowHelper.ThrowArgumentOutOfRangeException(ExceptionArgument.count, ExceptionResource.ArgumentOutOfRange_NeedNonNegNum);
    }
    if (this._size - index < count)
    {
        ThrowHelper.ThrowArgumentException(ExceptionResource.Argument_InvalidOffLen);
    }
    if (count > 0)
    {
        int size = this._size;
        this._size -= count;
        if (index < this._size)
        {
            Array.Copy(this._items, index + count, this._items, index, this._size - index);
        }
        Array.Clear(this._items, this._size, count);
        this._version++;
    }
}

public int RemoveAll(Predicate<T> match)
{
    if (match == null)
    {
        ThrowHelper.ThrowArgumentNullException(ExceptionArgument.match);
    }
    int num = 0;
    while (num < this._size && !match(this._items[num]))
    {
        num++;
    }
    if (num >= this._size)
    {
        return 0;
    }
    int i = num + 1;
    while (i < this._size)
    {
        while (i < this._size && match(this._items[i]))
        {
            i++;
        }
        if (i < this._size)
        {
            this._items[num++] = this._items[i++];
        }
    }
    Array.Clear(this._items, num, this._size - num);
    int result = this._size - num;
    this._size = num;
    this._version++;
    return result;
}

在删除单个元素上面这几个函数没有太大区别,唯一需要注意的是Remove函数中先调用IndexOf,然后在调用RemoveAt,最坏的情况会执行两边遍历。
在删除多个元素时,会常见到下面类似形式的代码,由于removeAt内部会调用循环拷贝数组,所以下面代码的复杂度为 O ( n 2 )
推荐使用RemoveAll(Predicate< T > match)进行多个元素的删除,值得一读的是RemoveAll 内部实现:
1. 首先找到第一个需要删除的元素的索引 num
2. 然后将 num 之后不需要删除的元素复制到需要删除的元素之前,这样就保证所要要删除的元素都在列表尾部,这样做的目的是尾部元素的删除,不需要进行拷贝。
3. 最后调用一次Array.Clear函数来完成整个过程。
相对于下面的代码,减少拷贝次数,时间复杂度从 O ( n 2 ) 减少到 O ( n )

for (int i = list.Count-1; i >= 0; i--)
{
    if (match(list[i]))
        list.RemoveAt(i);
}

其他

待补充

猜你喜欢

转载自blog.csdn.net/salvare/article/details/81584827