C# Queue源码剖析
来源:互联网 发布:非农数据分析 编辑:程序博客网 时间:2024/06/06 00:41
源代码版本为 .NET Framework 4.6.1
本系列持续更新,敬请关注
有投入,有产出。
(注:非基础性,主要涉及Queue的实现原理)
水平有限,若有不对之处,望指正。
Queue表示对象的先进先出集合。实现了ICollection接口,可以由数组或链表两种形式实现,在.NET中是以数组的形式实现的。
概念
队列是一种特殊的线性表,特殊之处在于它只允许在表头(head)进行删除操作,而在表尾(tail)进行插入操作。
队列的数据元素又称为队列元素。在队列中插入一个队列元素称为入队,从队列中删除一个队列元素成为出队。因为队列只允许在一段插入,在另一端删除,所以只有最早进入队列的元素才能最先从队列中删除,故队列又称为先进先出(FIFO—first in first out)线性表
队列可以分为顺序队列和循环队列,.NET中为了提高空间的利用率,采用的是循环队列。
循环队列
为充分利用向量空间,克服”假溢出”(由于入队和出队操作中,头尾指针只增加不减小,致使被删元素的空间永远无法重新利用)现象的方法是:将向量空间想象为一个首尾相接的圆环,并称这种向量为循环向量。存储在其中的队列称为循环队列(Circular Queue)。概念图如下:
循环队列中,由于入队时尾指针向前追赶头指针;出队时头指针向前追赶尾指针,造成空队列和满队列时头尾指针均相等。因此,无法通过条件front==rear来判别队列是”空”还是”满”,.NET使用一下方法判断空队列和满队列(实际.NET中,队列的长度时自动扩容的):
(1)私有成员_size = 0时,为空队列。
(2)_size == _array.Length时(_array为Queue内部维护的实际数据数组),为满队列,这个时候会进行自动扩容(新建一个2倍于原容量的数组)。
进入主题,上代码解说
基本成员
private T[] _array; private int _head; // 表头 private int _tail; // 表尾 private int _size; // 队列元素数量 private int _version; [NonSerialized] private Object _syncRoot; private const int _MinimumGrow = 4; // 最小增长值 private const int _ShrinkThreshold = 32; // 收缩阈值 private const int _GrowFactor = 200; // 每次增长双倍 private const int _DefaultCapacity = 4; // 默认容量 static T[] _emptyArray = new T[0]; //空数组
_head:一个指向队列头元素的索引,因为是循环列队,_head值有可能大于_tail值。
_tail:一个指向队列尾元素的索引,因为是循环列队,_tail值有可能笑于_head值。
_size:记录队列中元素的总数量。
_MinimumGrow: 队列每一次的最小扩容量。
_ShrinkThreshold:“迷”之收缩阈值。
_GrowFactor:增长因子。
_DefaultCapacity: 默认初始容量。
_emptyArray:默认空数组。
初始化函数
public Queue() { _array = _emptyArray; } public Queue(int capacity) { if (capacity < 0) ThrowHelper.ThrowArgumentOutOfRangeException(ExceptionArgument.capacity, ExceptionResource.ArgumentOutOfRange_NeedNonNegNumRequired); _array = new T[capacity]; _head = 0; _tail = 0; _size = 0; } public Queue(IEnumerable<T> collection){ if (collection == null) ThrowHelper.ThrowArgumentNullException(ExceptionArgument.collection); _array = new T[_DefaultCapacity]; _size = 0; _version = 0; using(IEnumerator<T> en = collection.GetEnumerator()) { while(en.MoveNext()) { Enqueue(en.Current); } } }
有三个初始化函数,一般初始化都会使用初始默认值_head和_tail最初都指向索引0(不代表_head=_tail就是空数组),_size=0长度为0。
入队
突然想起,军训期间被罚做完俯卧撑后,教官大喊入队。
入队的方法:Enqueue(T item)
public void Enqueue(T item) { //动态扩容 if (_size == _array.Length) { int newcapacity = (int)((long)_array.Length * (long)_GrowFactor / 100); if (newcapacity < _array.Length + _MinimumGrow) { newcapacity = _array.Length + _MinimumGrow; } SetCapacity(newcapacity); } _array[_tail] = item; _tail = (_tail + 1) % _array.Length; _size++; _version++; }
入队看起来很简单,通过_tail拿到队尾索引,将元素插入队尾即可。但是这里有一点小细节:
_tail = (_tail + 1) % _array.Length
下个元素的队尾索引 = (当前队尾索引 + 1) % 数据数组长度,这里的数据数组长度不等同与队列元素数量。此公式与抑制了队尾索引不会超过数据数组长度,从而避免了数据溢出的产生(同时也会导致队尾索引会小于队头索引,需要分情况进行处理)。
出队
出队有两种方法
(1)public T Peek():返回位于 Queue 开始处的对象但不将其移除。
(2)public T Dequeue():移除并返回位于 Queue 开始处的对象。
public T Peek() { if (_size == 0) ThrowHelper.ThrowInvalidOperationException(ExceptionResource.InvalidOperation_EmptyQueue); return _array[_head]; } public T Dequeue() { if (_size == 0) ThrowHelper.ThrowInvalidOperationException(ExceptionResource.InvalidOperation_EmptyQueue); T removed = _array[_head]; _array[_head] = default(T); _head = (_head + 1) % _array.Length; _size--; _version++; return removed; }
Peek()简单粗暴,通过索引直接返回数据。
Queue没有提供Remove方法,但是Dequeue具有删除功能并返回头元素。被移除的元素直接指向null(空引用)。并且头元素索引向前移动(这是一个跑圈圈的游戏,你懂的)。
查询
Contains(T item)判断队列中是否至少包含一个匹配的元素存在 是则返回true,否则返回false。
public bool Contains(T item) { int index = _head; int count = _size; EqualityComparer<T> c = EqualityComparer<T>.Default; while (count-- > 0) { if (((Object) item) == null) { if (((Object) _array[index]) == null) return true; } else if (_array[index] != null && c.Equals(_array[index], item)) { return true; } index = (index + 1) % _array.Length; } return false; }
容量调整
容量调整,可以重置队列空间,如果元素数小于当前容量的 90%,将容量设置为 Queue中的实际元素数。
public void TrimExcess() { int threshold = (int)(((double)_array.Length) * 0.9); if( _size < threshold ) { SetCapacity(_size); } }
private void SetCapacity(int capacity) { //创建新数组 T[] newarray = new T[capacity]; if (_size > 0) { if (_head < _tail) { //头索引小于尾索引 Array.Copy(_array, _head, newarray, 0, _size); } else { //头索引大于尾索引 Array.Copy(_array, _head, newarray, 0, _array.Length - _head); Array.Copy(_array, 0, newarray, _array.Length - _head, _tail); } } _array = newarray; _head = 0; _tail = (_size == capacity) ? 0 : _size; _version++; }
最后
(1)Queue可以通过TrimExcess()方法,将容量下降到实际元素的数量.
(2)Queue允许重复的元素。
(3)Queue和Stack主要是用来存储临时信息的。(下篇文章写Stack,一种后进先出的集合)
- C# Queue源码剖析
- STL 源码剖析 queue
- STL源码剖析---stack,queue
- 源码剖析之wait queue
- c# Queue源码解析
- STL 之 queue、priority_queue 源码剖析
- STL源码剖析----stack、heap、queue
- STL源码剖析——queue
- C# ArrayList源码剖析
- C# Hashtable源码剖析
- C# Dictionary源码剖析
- C# Stack源码剖析
- c# 源码深度剖析 list
- STL源码剖析之stack,queue容器【2013.11.25】
- STL源码剖析——容器配接器之queue
- STL源码剖析 - 第4章 序列式容器 - queue
- stl源码剖析 详细学习笔记stack queue
- 【Nginx源码剖析-数据结构】双向链表(queue)【未完】
- android 保存网络图片到本地
- 浅谈Java反射
- Spark Streaming 新手指南(原始文章已经发布表在IBM Developworks)
- sentinel监控主从服务器
- CodeForces 667A Pouring Rain
- C# Queue源码剖析
- HDU1501zipper
- Property’s synthesized getter follows Cocoa naming
- CodeForces 668B Little Artem and Dance
- zookeeper原理(转)
- hrbust/哈理工oj 2220 强迫症【思维】
- c++ auto_ptrs
- fatal error: wiring.h: No such file or directory
- Luogu1181数列分段