讲究地使用 List

本篇旨意在于讨论List的基本用法,不做全面讲解,仅仅涉及构造函数List、Add、RemoveAt

先看看这几个函数的代码

1、构造函数

        static readonly T[]  _emptyArray = new T[0];
private const int _defaultCapacity = 4; // Constructs a List. The list is initially empty and has a capacity // of zero. Upon adding the first element to the list the capacity is // increased to 16, and then increased in multiples of two as required. public List() { _items = _emptyArray; } // Constructs a List with a given initial capacity. The list is // initially empty, but will have room for the given number of elements // before any reallocations are required. // 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]; } // Constructs a List, copying the contents of the given collection. The // size and capacity of the new list will both be equal to the size of the // given collection. // 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; // This enumerable could be empty. Let Add allocate a new array, if needed. // Note it will also go to _defaultCapacity first, not 1, then 2, etc. using(IEnumerator<T> en = collection.GetEnumerator()) { while(en.MoveNext()) { Add(en.Current); } } } }

 2、Add

 
 

public void Add(T item) {
  if (_size == _items.Length) EnsureCapacity(_size + 1);
  _items[_size++] = item;
  _version++;
}

private void EnsureCapacity(int min) {
  if (_items.Length < min) {
    int newCapacity = _items.Length == 0? _defaultCapacity : _items.Length * 2;
    // Allow the list to grow to maximum possible capacity (~2G elements) before encountering overflow.
    // Note that this check works even when _items.Length overflowed thanks to the (uint) cast
    if ((uint)newCapacity > Array.MaxArrayLength) newCapacity = Array.MaxArrayLength;
    if (newCapacity < min) newCapacity = min;
    Capacity = newCapacity;
  }
}

public int Capacity {
  get {
    Contract.Ensures(Contract.Result<int>() >= 0);
    return _items.Length;
  }
  set {
    if (value < _size) {
      ThrowHelper.ThrowArgumentOutOfRangeException(ExceptionArgument.value, ExceptionResource.ArgumentOutOfRange_SmallCapacity);
    }
    Contract.EndContractBlock();

    if (value != _items.Length) {
      if (value > 0) {
        T[] newItems = new T[value];
        if (_size > 0) {
          Array.Copy(_items, 0, newItems, 0, _size);
        }
        _items = newItems;
      }
      else {
        _items = _emptyArray;
      }
    }
  }
}

3、RemoveAt

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++;
}

上面是.Net4.7.2的代码

private List<CDBot> cdObjList = new List<CDBot>();

public void RemoveByItem(CDBot item)
{
    if (item.index != -1)
    {
        if (cdObjList.Count > item.index)
        {
             cdObjList.RemoveAt(item.index);
             for (int i = item.index; i < cdObjList.Count; i++)
             {
                  --cdObjList[i].index;
             }
             item.index = -1;
        }
   }
}

这段代码是段管理计时器的代码,CDBot.index是其在 cdObjList中的下标利于管理。经过观察,cdObjList在平稳运行时,Cout稳定在100左右。

构造函数的源码告诉我们,调用一个不带参数的List构造函数,生成的是一个空的数组,接下来调用Add后通过更新Capacity重新new一个数组实现容量的自增长,默认长度为4,两倍的速度增长,

那么cdObjList的实际容量变化将会是0、4、8、16、32、64、128。重新生成数组意味着之前的需要被丢弃,C#的堆内存是GC制而非软件工程师自行掌控,那么之前被丢弃的内容就成为了GC的对象。

针对上面我做出如下修改

private List<CDBot> cdObjList = new List<CDBot>(128);
private const int CDObjInvalidIndex = -1;

public void RemoveByItem(CDBot item)
{
    if (item.index != CDObjInvalidIndex && cdObjList.Count > item.index)
    {
        cdObjList.RemoveAt(item.index);
        for (int i = item.index; i < cdObjList.Count; i++)
        {
             --cdObjList[i].index;
        }
        item.index = CDObjInvalidIndex;
    }
}

给定一个初始容量,减少List自增长造成的数组更换,但这样做也有个问题,也许随着项目的开发,List的稳定容量将会发生变化,以我目前的想法可以封装一下List的构造、Add,对Cout进行监测,在Debug模式下提醒工程师重新设置List的初始容量。

List的实现是用数组,那么删除中间的元素需要将后面的元素前移。测试发现 cdObjList 执行 Remove 时元素命中范围有60%在20~70之间,对此作出如下修改

        private List<CDBot> cdObjList = new List<CDBot>(128);
        private const int CDObjInvalidIndex = -1;

        public void RemoveByItem(CDBot item)
        {
            if (item.index != CDObjInvalidIndex && cdObjList.Count > item.index)
            {
                cdObjList[item.index] = cdObjList[cdObjList.Count - 1];
                cdObjList[item.index].index = item.index;
                item.index = CDObjInvalidIndex;
                cdObjList.RemoveAt(cdObjList.Count - 1);
            }
        }

将末尾的元素覆盖需要删除的元素,改为删除最后一个元素从而达到避免元素前移带来的消耗,但此做法具有List的元素非排序状态的局限性。

        private void DealLevelUpMaterials(uint level, int type)
        {
            var levelUpCfg = materialCfg.GetConfigureData(level, type);
            List<MaterialData> NeedMaterials = levelUpCfg.NeedMaterials;
            for (int i = 0; i < NeedMaterials.Count; ++i)
            {
                List<PBDelItemata> itemList;
                var material = NeedMaterials[i];
                if (BagCtr.Model.TryGetItemSNList((uint)material.id, (uint)material.count, out itemList))
                {
                    //do something
                }
            }
        }

        public bool TryGetItemSNList(uint itemID, uint needCount, out List<PBDelItemata> itemList)
        {
            itemList = new List<PBDelItemata>();
            if (GetItemCount((uint)itemID) == 0)
            {
                return false;
            }

            List<BaseItem> items = GetItemById(itemID);
            while (needCount > 0)
            {
                for (int i = 0; i < items.Count; i++)
                {
                    //do something
                    //itemList.Add(delItem);
                }
            }
            return true;
        }
上面的这段代码 DealLevelUpMaterials 中的 itemList 会在 TryGetItemSNList 中分配实体,其分配次数取决于 for ,联系上下文,这个List可复用

如下修改
        private void DealLevelUpMaterials(uint level, int type)
        {
            var levelUpCfg = materialCfg.GetConfigureData(level, type);
            List<MaterialData> NeedMaterials = levelUpCfg.NeedMaterials;
            List<PBDelItemata> itemList = new List<PBDelItemata>(8);
            for (int i = 0; i < NeedMaterials.Count; ++i)
            {
                var material = NeedMaterials[i];
                itemList.Clear();
                if (BagCtr.Model.TryGetItemSNList((uint)material.id, (uint)material.count, ref itemList))
                {
                    //do something
                }
            }
        }

        public bool TryGetItemSNList(uint itemID, uint needCount, ref List<PBDelItemata> itemList)
        {
            if (GetItemCount((uint)itemID) == 0)
            {
                return false;
            }

            List<BaseItem> items = GetItemById(itemID);
            while (needCount > 0)
            {
                for (int i = 0; i < items.Count; i++)
                {
                    //do something
                    //itemList.Add(delItem);
                }
            }
            return true;
        }

如有错误欢迎指正

猜你喜欢

转载自www.cnblogs.com/haihuahuang/p/10354004.html