Stack简介
Stack是一个通用集合类,用于存储具有先进后出特性的一组对象。底层实现依赖于动态数组,并通过一系列方法提供基本的栈操作,例如入栈、出栈和查看栈顶元素。
Stack源码分析
1、基本结构
- _array:使用泛型数组存储元素
- _size:表示栈中当前元素的数量
- _version:用于跟踪栈的修改版本,以支持迭代器的正确操作
public class Stack<T> : IEnumerable<T>, ICollection, IReadOnlyCollection<T>
{
// 使用泛型数组存储栈中的元素
private T[] _array;
// 当前栈中元素的数量
private int _size;
// 跟踪栈的修改版本,用于支持迭代器
private int _version;
// 默认初始容量
private const int _defaultCapacity = 4;
public Stack()
{
// 初始化一个空数组
_array = Array.Empty<T>();
_size = 0;
_version = 0;
}
public Stack(int capacity)
{
if (capacity < 0)
throw new ArgumentOutOfRangeException(nameof(capacity));
// 初始化一个指定容量的数组
_array = new T[capacity];
_size = 0;
_version = 0;
}
public Stack(IEnumerable<T> collection)
{
if (collection == null)
throw new ArgumentNullException(nameof(collection));
if (collection is ICollection<T> c)
{
int count = c.Count;
if (count == 0)
{
_array = Array.Empty<T>();
}
else
{
_array = new T[count];
c.CopyTo(_array, 0);
_size = count;
}
}
else
{
_size = 0;
_array = Array.Empty<T>();
foreach (T item in collection)
{
Push(item);
}
}
}
}
2、动态扩容机制
- 当栈中元素数量达到数组容量时,会自动扩展数组的大小,通常是将当前容量翻倍,以减少频繁的内存分配操作。
public void Push(T item)
{
if (_size == _array.Length) EnsureCapacity(_size + 1);
_array[_size++] = item;
_version++;
}
private void EnsureCapacity(int min)
{
if (_array.Length < min)
{
int newCapacity = _array.Length == 0 ? _defaultCapacity : _array.Length * 2;
if ((uint)newCapacity > Array.MaxArrayLength) newCapacity = Array.MaxArrayLength;
if (newCapacity < min) newCapacity = min;
T[] newArray = new T[newCapacity];
Array.Copy(_array, 0, newArray, 0, _size);
_array = newArray;
}
}
上述Stack源码中的Push函数,每次增加一个元素的数据,Push接口都会先检查容量够不够,如果不够则调用EnsureCapacity来增加容量。根据这段代码可以看出每次容量不够的时候,整个数组的容量都会扩充一倍,_defaultCapacity 是容量的默认值为4。因此整个扩充的路线为4,8,16,32,64,128,256,512,1024…依次类推。
因为Stack使用数组作为底层数据结构,数组的好处时使用索引查找元素很快,但是在扩容的时候需要重新new一个新的数组,将原来的数据拷贝过去,每次new新数组都会造成内存垃圾,这给垃圾回收GC带来了很大的负担。
这里使用2的指数倍进行扩容,可以减少扩容的次数,不过频繁的使用Add时不断的扩容还是增加GC的负担,而且当数量使用不当时会浪费大量的内存空间,例如元素数量为1025个时,List就会扩容到2048个元素。
3、栈操作方法
-
Pop:移除并返回栈顶元素。
public T Pop() { if (_size == 0) throw new InvalidOperationException("Stack empty"); _version++; T item = _array[--_size]; if (RuntimeHelpers.IsReferenceOrContainsReferences<T>()) { _array[_size] = default!; // Clear the slot to release the reference. } return item; }
-
Peek:返回栈顶元素但不移除。
public T Peek() { if (_size == 0) throw new InvalidOperationException("Stack empty"); return _array[_size - 1]; }
-
Contains:检查栈中是否包含指定元素
public bool Contains(T item) { if (item == null) { for (int i = 0; i < _size; i++) { if (_array[i] == null) return true; } } else { EqualityComparer<T> c = EqualityComparer<T>.Default; for (int i = 0; i < _size; i++) { if (c.Equals(_array[i], item)) return true; } } return false; }
-
Clear:清空栈中的所有元素。
public void Clear() { if (RuntimeHelpers.IsReferenceOrContainsReferences<T>()) { Array.Clear(_array, 0, _size); // Clear the elements so that the gc can reclaim the references. } _size = 0; _version++; }
-
ToArray:将栈中的元素赋值到一个新数组中。
public T[] ToArray() { T[] objArray = new T[_size]; for (int i = 0; i < _size; i++) { objArray[i] = _array[_size - i - 1]; } return objArray; }