剖析.NET Framework源码—数据结构List<T> (一)

List<T>类表示可通过索引访问的对象的强类型列表。提供对列表进行搜索,排序和操作的方法。

 

//List<T>内部使用一个泛型数组T[]来存储元素。为这个内部数组分配的长度就    

  是List的容量Capacity(最大能存储的元素个数),当向这个List添加新元素

  时.如果List已满或无法再添加更多元素,该List的容量就会按照某种机制自动

  增长以便容纳更多的新元素,其容量的增长是通过重新分配内部数组T[]实现的。

    public class List<T> : IList<T>, System.Collections.IList 

    {

 

        private const int _defaultCapacity = 4;

 

        private T[] _items;

        private int _size;    

        private int _version;

 

        static T[] _emptyArray = new T[0];

 }

 

以上是List<T>的四个私有字段和一个静态字段:

(1)_items 就是List内部实际存储元素的地方——数组。我们往List里面添加元素,其实就是将元素放到List内部的数组中。

(2)_size 表示内部数组中实际存储的元素个数。注意这个_size与Capacity的区别!

(3)_version可以理解为版本号。这个版本号存在的目的是为了保证在对该List进行迭代的过程中不对其原有元素进行修改。

(4)_defaultCapacity表示该List默认的容量。

(5)_emptyArray就是一个长度为0的空数组!

 

【构造函数】

接着我们看看List的构造函数:

(1)默认无参构造函数 

//构造一个List。这个List初始为空并且容量为0。

        public List()

        {

            _items = _emptyArray;

        }

 

(2)参数表示List的容量的构造函数

        //用一个指定的初始容量capacity构造List。

        public List(int capacity)

        {

            if (capacity < 0) 

                throw new ArgumentOutOfRangeException();

            _items = new T[capacity];

        }

(3)用一个实现了IEnumerable接口的collection构造List

//构造一个List,并将collection中的所有元素复制到List中,这个新List  的size和capacity都等于给定collection的大小。

        public List(IEnumerable<T> collection)

        {

            if (collection == null)

                throw new ArgumentNullException();

 

            ICollection<T> c = collection as ICollection<T>;

            if (c != null)

            {

                int count = c.Count;

                _items = new T[count];

                c.CopyTo(_items, 0);

                _size = count;

            }

            else

            {

                _size = 0;

                _items = new T[_defaultCapacity];

 

                using (IEnumerator<T> en = collection.GetEnumerator())

                {

                    while (en.MoveNext())

                    {

                        Add(en.Current);

                    }

                }

            }

        }

让我们来分析一下这个相对比较复杂的构造函数。首先进行的是参数合法性验证,这里只简单的检查了传入的collection是否为空;紧接着会尝试着将collection转换成ICollection接口,如果转换成功,执行if语句中的代码,转换失败则执行else语句中的代码!

假设转换成功,则说明传入的参数collection是实现了ICollection接口的。接下来的代码很简单,大概就是分配一个数组,该数组的长度就是collection中元素的个数,接着把collection中的元素全部拷贝到新分配的数组中。

如果转换失败,那么执行else语句块。首先会按照默认容量_defaultCapacity分配一个新数组,接着利用collection的枚举器将collection中的元素一个一个添加到新分配的数组中。

 

【属性】

接着,我们介绍List中几个比较重要的属性

(1)Capacity:代表List的容量。即是其内部数组_items的长度。

//获取或设置该List的容量,当设置时,其内部数组会按照指定value重新分  配。 

        public int Capacity

        {

            get { return _items.Length; }

 

            set

            {

                if (value != _items.Length)

                {

                    if (value < _size)

                    {

                        throw new ArgumentOutOfRangeException();

                    }

 

                    if (value > 0)

                    {

                        T[] newItems = new T[value];   //reallocate

 

                amp;nbsp;        if (_size > 0)

                        {

                            Array.Copy(_items, 0, newItems, 0, _size);

                        }

                        _items = newItems;

                    }

                    else

                    {

                        _items = _emptyArray;

                    }

                }

            }

        }

当设置Capacity时,首先会进行合法性验证,值得注意的是,当要设置的Capacity的值value小于当前List的实际元素个数_size时,会抛出异常!接着,按照value的值重新分配一个新数组newItems,然后把原来数组_items中的所有元素(_size个)全部拷贝到新数组newItems中。旧数组随后被抛弃,等待GC回收。

(2)Count

 //只读属性。简单的返回_size,用于描述List中实际有多少个元素。

        public int Count

        {

            get { return _size; }

        }

(3)Item:获取或设置指定索引处的元素

 public T this[int index]

        {

            get

            {

 

                if ((uint)index >= (uint)_size)

                {

                   throw new ArgumentOutOfRangeException();

                }

                return _items[index];

            }

            set

            {

                if ((uint)index >= (uint)_size)

                {

                    throw new ArgumentOutOfRangeException();

                }

                _items[index] = value;

                _version++;

            }

        }

    以上代码都很简单,值得说明的一点是当每次设置指定索引处的元素值时,字段_version都会加1,表示该List又被修改了一次。其具体作用会在List的迭代器中详细说明。

重要方法】

首先,我们展示两个私有方法:

(1)IsCompatibleObject 

//判断对象value的类型是否与数组类型T相容
private static bool IsCompatibleObject(object value)

        {

            if ((value is T) || (value == null && !typeof(T).IsValueType))

            {

                return true;

            }

            return false;

        }

可以看到。类型value与数组类型T相容有以下2种情况:

1. value的类型就是T类型或是T类型的子类

2. value为null时,数组类型T必须是引用类型

 

(2)VerifyValueType

//利用上面的IsCompatibleObject方法进行对象类型相容性检查

private static void VerifyValueType(object value)

        {

            if (!IsCompatibleObject(value))

            {

                throw new ArgumentException();

            }

        }

 

然后,我们看看在List中比较重要的公共方法:

首先,我们看一个List用于扩展其容量的方法,这个方法很重要,决定了List的性能。

(1)EnsureCapacity

//确保List的容量至少为给定的最小值min。

//如果List的当前容量比给定最小值min小,那 么其容量会增至当前容量的两倍,或是增至最小值min。

        private void EnsureCapacity(int min)

        {

            if (_items.Length < min)

            {

                int newCapacity = _items.Length == 0 ? _defaultCapacity : _items.Length * 2;

                if (newCapacity < min) newCapacity = min;

                Capacity = newCapacity;

            }

        }

上面的代码首先确定List新的容量newCapacity是多少,然后设置List的属性Capacity的值为newCapacity。

newCapacity值的确定过程是很简单却又十分曲折!

1. 如果当前的List是才被new出来,还没有添加任何元素,那么_items.Length等于0,

  此时newCapacity的值会被设置为默认容量_defaultCapacity=4。

2. 如果数组中已经有了元素,那么newCapacity的值会被设置为当前容量的两倍。

3. 如果经过计算后newCapacity仍然小于给定的最小值min,那么就直接把newCapacity的值设置为给定的最小值min。

posted on 2010-02-03 01:40  张念  阅读(1005)  评论(0编辑  收藏  举报

导航