您的位置:首页 > 编程语言 > C#

C# Stack源码剖析

2016-05-10 15:41 489 查看
源代码版本为 .NET Framework 4.6.1

本系列持续更新,敬请关注

有投入,有产出。

(注:非基础性,主要涉及Stack的实现原理)

水平有限,若有不对之处,望指正。

Stack(栈)表示对象的后进先出 (LIFO) 集合。实现了ICollection接口。

概念

定义:限定仅在表尾进行插入或删除操作的线性表,表尾对应栈顶,表头对应栈底,不含元素的栈称为空栈。

入栈:往栈顶插入一个元素。

出栈:在栈顶删除一个元素

元素的操作只能在栈顶进行,最后入栈的元素最先出栈,结构图如下:



进入主题

基本成员

private T[] _array;     // 用于存储栈元素的数组
private int _size;           // 栈元素的数量
private const int _defaultCapacity = 4; // 默认初始容量
static T[] _emptyArray = new T[0];  // 空数组,用于赋默认值


构造函数

Stack有三个构造函数,分别为:

(1)全部使用Stack设定的默认值,不推荐使用(原因是动态扩容需要额外的计算与开辟新的内存空间,动态扩容应该发生在超出预期容量值范围的情况下,抑制溢出);

public Stack() {
_array = _emptyArray;
_size = 0;
_version = 0;
}


(2)使用一个非负的整数设定Stack的一个初始容量,推荐使用(给定一个预期的容量值,若预期值小了,会自动扩容,也不担心溢出)

public Stack(int capacity) {
if (capacity < 0)
ThrowHelper.ThrowArgumentOutOfRangeException(ExceptionArgument.capacity, ExceptionResource.ArgumentOutOfRange_NeedNonNegNumRequired);
_array = new T[capacity];
_size = 0;
_version = 0;
}


(3)使用一个现有的非空引用集合进行填充初始化Stack,Stack具有与该集合相同的长度,并且按照集合元素的存储顺序(推荐使用实现了Collection泛型接口的集合,可以获得一些性能上的提升)。

public Stack(IEnumerable<T> collection)
{
if (collection==null)
ThrowHelper.ThrowArgumentNullException(ExceptionArgument.collection);

ICollection<T> c = collection as ICollection<T>;
if( c != null) {
//实现了ICollection泛型接口的集合,简单粗暴,分配空间,数组复制
int count = c.Count;
_array = new T[count];
c.CopyTo(_array, 0);
_size = count;
}
else {
_size = 0;
//没有实现ICollection泛型接口的,默认初始容量为4,空间不够还需动态扩容
_array = new T[_defaultCapacity];

//遍历元素 执行入栈操作
using(IEnumerator<T> en = collection.GetEnumerator()) {
while(en.MoveNext()) {
Push(en.Current);
}
}
}
}


入栈

入栈是Stack最核心的方法之一,将元素从栈顶插入到Stack中。触发动态扩容的条件当且仅当元素数量等于内部存储元素的数组长度时,开辟一个新的内存空间,原来的2倍,原来的内存空间将被GC回收并释放资源

public void Push(T item) {
//动态扩容
if (_size == _array.Length) {
T[] newArray = new T[(_array.Length == 0) ? _defaultCapacity : 2*_array.Length];
Array.Copy(_array, 0, newArray, 0, _size);
_array = newArray;
}
//将元素存于栈顶位置
_array[_size++] = item;
_version++;
}


出栈

出栈是Stack最核心的方法之一,获取位于栈顶的元素,空栈不具备出栈功能(强制使用将会引发异常)。分为两种:

(1)只获取栈顶元素,不移除。

public T Peek() {
if (_size==0)
ThrowHelper.ThrowInvalidOperationException(ExceptionResource.InvalidOperation_EmptyStack);
return _array[_size-1];//获取栈顶元素
}


(2)获取栈顶元素并将其移除。

public T Pop() {
if (_size == 0)
ThrowHelper.ThrowInvalidOperationException(ExceptionResource.InvalidOperation_EmptyStack);
_version++;
T item = _array[--_size];       // 获取栈顶元素,同时栈顶指针前移一位
_array[_size] = default(T);     // 释放栈顶的内存空间
return item;
}


空间压缩

如果实际元素数量小于当前容量的 90%,将容量设置为 Stack中的实际元素数。推荐在确定不会再有太多的入栈元素时并且初始化容量相比预期值大得多时使用。

public void TrimExcess() {
int threshold = (int)(((double)_array.Length) * 0.9);
if( _size < threshold ) {
T[] newarray = new T[_size];
Array.Copy(_array, 0, newarray, 0, _size);
_array = newarray;
_version++;
}
}


遍历

Stack实现了IEnumerable泛型接口的GetEnumerator(),返回一个结构体:

public struct Enumerator : IEnumerator<T>, System.Collections.IEnumerator{
private Stack<T> _stack;    //遍历的当前栈
private int _index;        // 遍历标识
private int _version;
private T currentElement;  //当前栈元素

//初始化方法
internal Enumerator(Stack<T> stack) {
_stack = stack;
_version = _stack._version;
_index = -2;
currentElement = default(T);
}

//指针移动,遍历必须的方法
public bool MoveNext() {
bool retval;
if (_version != _stack._version) ThrowHelper.ThrowInvalidOperationException(ExceptionResource.InvalidOperation_EnumFailedVersion);
if (_index == -2) {  // 开始遍历
_index = _stack._size-1;
retval = ( _index >= 0);
if (retval)
currentElement = _stack._array[_index];
return retval;
}
if (_index == -1) {  // 结束遍历
return false;
}

retval = (--_index >= 0);
if (retval)
currentElement = _stack._array[_index];
else
currentElement = default(T);
return retval;
}

//获取遍历的当前元素,与MoveNext结合使用,即遍历必须的属性
public T Current {
get {
if (_index == -2) ThrowHelper.ThrowInvalidOperationException(ExceptionResource.InvalidOperation_EnumNotStarted);
if (_index == -1) ThrowHelper.ThrowInvalidOperationException(ExceptionResource.InvalidOperation_EnumEnded);
return currentElement;
}
}
}
内容来自用户分享和网络整理,不保证内容的准确性,如有侵权内容,可联系管理员处理 点击这里给我发消息