讲究地使用 List
Posted haihuahuang
tags:
篇首语:本文由小常识网(cha138.com)小编为大家整理,主要介绍了讲究地使用 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; }
如有错误欢迎指正
以上是关于讲究地使用 List的主要内容,如果未能解决你的问题,请参考以下文章
Android获取各个应用程序的缓存文件代码小片段(使用AIDL)
TP5报如下的错误 Indirect modification of overloaded element of thinkpaginatorCollection has no effect(代码片段