C# Stack源码剖析

源代码版本为 .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;
                }
            }
        }
时间: 2024-11-09 02:43:19

C# Stack源码剖析的相关文章

STL 之 stack 源码剖析

G++ 2.91.57,cygnus\cygwin-b20\include\g++\stl_stack.h 完整列表 /* * * Copyright (c) 1994 * Hewlett-Packard Company * * Permission to use, copy, modify, distribute and sell this software * and its documentation for any purpose is hereby granted without fe

STL&quot;源码&quot;剖析-重点知识总结

STL是C++重要的组件之一,大学时看过<STL源码剖析>这本书,这几天复习了一下,总结出以下LZ认为比较重要的知识点,内容有点略多 :) 1.STL概述 STL提供六大组件,彼此可以组合套用: 容器(Containers):各种数据结构,如:vector.list.deque.set.map.用来存放数据.从实现的角度来看,STL容器是一种class template. 算法(algorithms):各种常用算法,如:sort.search.copy.erase.从实现的角度来看,STL算法

通读《STL源码剖析》之后的一点读书笔记

[QQ群: 189191838,对算法和C++感兴趣可以进来] 直接逼入正题. Standard Template Library简称STL.STL可分为容器(containers).迭代器(iterators).空间配置器(allocator).配接器(adaptors).算法(algorithms).仿函数(functors)六个部分. 迭代器和泛型编程的思想在这里几乎用到了极致.模板或者泛型编程其实就是算法实现时不指定具体类型,而由调用的时候指定类型,进行特化.在STL中,迭代器保证了ST

Python源码剖析笔记6-函数机制

Python的函数机制是很重要的部分,很多时候用python写脚本,就是几个函数简单解决问题,不需要像java那样必须弄个class什么的. 本文简书地址:http://www.jianshu.com/p/d00108741a18 1 函数对象PyFunctionObject PyFunctionObject对象的定义如下: typedef struct { PyObject_HEAD PyObject *func_code; /* A code object */ PyObject *func

STL源码剖析 --- 空间配置器 std::alloc

STL是建立在泛化之上的.数组泛化为容器,参数化了所包含的对象的类型.函数泛化为算法,参数化了所用的迭代器的类型.指针泛化为迭代器,参数化了所指向的对象的类型.STL中的六大组件:容器.算法.迭代器.配置器.适配器.仿函数. 这六大组件中在容器中分为序列式容器和关联容器两类,正好作为STL源码剖析这本书的内容.迭代器是容器和算法之间的胶合剂,从实现的角度来看,迭代器是一种将operator*.operator->.operator++.operator-等指针相关操作予以重载的class tem

《STL源码剖析》---stl_alloc.h阅读笔记

这一节是讲空间的配置与释放,但不涉及对象的构造和析构,只是讲解对象构造前空前的申请以及对象析构后空间怎么释放. SGI版本的STL对空间的的申请和释放做了如下考虑: 1.向堆申请空间 2.考虑了多线程.但是这节目的只是讲解空间配置与释放,因此忽略了多线程,集中学习空间的申请和释放. 3.内存不足时的应变措施 4.考虑到了内存碎片的问题.多次申请释放小块内存可能会造成内存碎片. 在C++中,内存的申请和释放是通过operator new函数和operator delete函数,这两个函数相当于C语

《python源码剖析》笔记 python虚拟机中的函数机制

本文为senlie原创,转载请保留此地址:http://blog.csdn.net/zhengsenlie 1.Python虚拟机在执行函数调用时会动态地创建新的 PyFrameObject对象, 这些PyFrameObject对象之间会形成PyFrameObject对象链,模拟x86平台上运行时栈 2.PyFuctionObject对象 typedef struct { PyObject_HEAD PyObject *func_code: //对应函数编译后的PyCodeObject对象 Py

Android多线程研究(1)——线程基础及源码剖析

从今天起我们来看一下Android中的多线程的知识,Android入门容易,但是要完成一个完善的产品却不容易,让我们从线程开始一步步深入Android内部. 一.线程基础回顾 package com.maso.test; public class TraditionalThread { public static void main(String[] args) { /* * 线程的第一种创建方式 */ Thread thread1 = new Thread(){ @Override publi

《python源码剖析》笔记 Python的编译结果

本文为senlie原创,转载请保留此地址:http://blog.csdn.net/zhengsenlie 1.python的执行过程 1)对python源代码进行编译,产生字节码 2)将编译结果交给python虚拟机,由虚拟机按照顺序一条一条地执行字节码,产生执行结果 图7-1 2.Python编译器的编译结果--PyCodeObject对象 Python编译器的编译结果中包含了字符串.常量值.字节码等在源代码中出现的一切有用的静态信息. 在Python运行期间,这些静态信息被PyCodeOb