前言
簡單介紹一下list。
正文
這裡以list
private static readonly T[] s_emptyArray = new T[0];
public List()
{
this._items = List<T>.s_emptyArray;
}
list 本質是一個陣列。
同樣我們可以指定容量,如果我們知道了我們大概需要多少資料,那麼我們可以指定一下,這樣避免了resize的損耗。
就跟我們作業系統一樣,提前申請記憶體大小。所以我們程式一般都有一個申請記憶體,實際使用記憶體,記憶體碎片這幾個概念。
新增也是很簡單哈。
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);
}
判斷是否滿了,如果沒滿直接存到陣列裡面去,如果滿了,那麼resize一下。
看下resize。
private void AddWithResize(T item)
{
int size = this._size;
this.EnsureCapacity(size + 1);
this._size = size + 1;
this._items[size] = item;
}
然後看一下擴容步驟。
private void EnsureCapacity(int min)
{
if (this._items.Length >= min)
return;
int num = this._items.Length == 0 ? 4 : this._items.Length * 2;
if ((uint) num > 2146435071U)
num = 2146435071;
if (num < min)
num = min;
this.Capacity = num;
}
首先在做了一次判斷,判斷是否容量夠用,所以是size+1。
if (this._items.Length >= min)
return;
這裡就有人問了外面不是判斷了,為什麼裡面還有判斷。
這個就是一些人喜歡談效能的地方了,認為多此一舉,如果裡面不判斷那麼就不是一個成熟的方法,提現不出方法的封閉性,因為方法的作用是之和引數打交道,外面是什麼其實是不管的。
那麼可以看出,一開始是4,然後後面就是翻倍了。
然後重點看下:
this.Capacity = num;
這個this.Capacity 並不是普通的變數,而是一個屬性哈,不然你都納悶它是怎麼擴容了。
public int Capacity
{
get => _items.Length;
set
{
if (value < _size)
{
ThrowHelper.ThrowArgumentOutOfRangeException(ExceptionArgument.value, ExceptionResource.ArgumentOutOfRange_SmallCapacity);
}
if (value != _items.Length)
{
if (value > 0)
{
T[] newItems = new T[value];
if (_size > 0)
{
Array.Copy(_items, newItems, _size);
}
_items = newItems;
}
else
{
_items = s_emptyArray;
}
}
}
}
首先判斷了不能縮容,如果縮容直接異常,其次我們注意道這個Capacity 是piblic的,也就是說我們在外部就可以直接呼叫。
後面邏輯就很簡單建立一個新的陣列,然後複製就ok了,然後重新賦值_items。
那麼來看一下remove吧。
public bool Remove(T item)
{
int index = IndexOf(item);
if (index >= 0)
{
RemoveAt(index);
return true;
}
return false;
}
首先是找到其位置:
public int IndexOf(T item)
=> Array.IndexOf(_items, item, 0, _size);
int IList.IndexOf(object? item)
{
if (IsCompatibleObject(item))
{
return IndexOf((T)item!);
}
return -1;
}
可以看一下這個IsCompatibleObject,還是很有趣的。
private static bool IsCompatibleObject(object? value)
{
// Non-null values are fine. Only accept nulls if T is a class or Nullable<U>.
// Note that default(T) is not equal to null for value types except when T is Nullable<U>.
return (value is T) || (value == null && default(T) == null);
}
從這個說明,其實我們是可以傳空物件的。
static void Main(string[] args)
{
List<object> lists = new List<object>();
lists.Add(null);
Console.WriteLine(lists.Count);
lists.Remove(null);
Console.ReadLine();
}
那麼來看一下removeat吧。
public void RemoveAt(int index)
{
if ((uint)index >= (uint)_size)
{
ThrowHelper.ThrowArgumentOutOfRange_IndexException();
}
_size--;
if (index < _size)
{
Array.Copy(_items, index + 1, _items, index, _size - index);
}
if (RuntimeHelpers.IsReferenceOrContainsReferences<T>())
{
_items[_size] = default!;
}
_version++;
}
這裡可以看出list的remove操作還是效能損耗很大的,尤其是大的list。
這裡有沒有注意道一個_version,這個有什麼作用呢?
當遍歷的時候我們就用的到。
internal Enumerator(List<T> list)
{
_list = list;
_index = 0;
_version = list._version;
_current = default;
}
public void Dispose()
{
}
public bool MoveNext()
{
List<T> localList = _list;
if (_version == localList._version && ((uint)_index < (uint)localList._size))
{
_current = localList._items[_index];
_index++;
return true;
}
return MoveNextRare();
}
private bool MoveNextRare()
{
if (_version != _list._version)
{
ThrowHelper.ThrowInvalidOperationException_InvalidOperation_EnumFailedVersion();
}
_index = _list._size + 1;
_current = default;
return false;
}
重點看上面的list,上面表面了,當我們使用foreach 進行遍歷的時候,如果我們進行了刪除或者新增,那麼_version就會發生變化,那麼可想而知會丟擲異常。
例子:
static void Main(string[] args)
{
List<object> lists = new List<object>();
lists.Add("123456");
lists.Add("1231246");
lists.Add("dsadadsads");
lists.Add("eqewqew");
foreach (var item in lists)
{
if (item.ToString() == "1231246")
{
lists.Remove(item);
}
}
Console.ReadLine();
}
然後就會丟擲異常了。
那麼這裡就不介紹find了,find 就是遍歷陣列,找出是否相等。
哦,對了講另外一個故事。
public int Count => _size;
count-1 就是當前插入的位置。
那麼如果你想刪除某個元素的時候,那麼你可以進行removeat 刪除,這樣避免了find。
那麼非常值得注意的是如果刪除了其他元素,如果那麼元素的位置小於你記錄的位置,那麼應該是位置進行減一。
結
以上僅是個人整理,如有錯誤望請指點。這個系列後面也會整理一下集合相關的原始碼。