Queue<T> 是 .NET 中实现队列(先进先出)的一种泛型集合类。它基于数组实现,支持动态扩容、线程不安全,适用于大多数需要队列结构的场景。


一、类结构与字段说明

public class Queue<T> : IEnumerable<T>, ICollection, IReadOnlyCollection<T>{private T[] _array;private int _head;       //指向队列中最先进入的元素位置,执行出队(Dequeue)操作时使用此索引private int _tail;       // 指向下一个要插入的位置,而不是最后一个元素的位置private int _size;       // 元素个数private int _version;private const int MinimumGrow = 4;private const int GrowFactor = 200;  // double each time}
  • 实现了 IEnumerable<T>ICollectionIReadOnlyCollection<T> 接口,支持枚举、集合操作和只读访问。
  • 内部使用环形数组(循环队列)实现,避免频繁复制和移动数据。

核心字段:

字段名类型说明
_arrayT[]存储元素的数组
_headint队头索引(下一个要出队的元素)
_tailint队尾索引(下一个要入队的位置)
_sizeint当前队列中元素个数
_versionint用于枚举器版本控制,防止修改时遍历异常
MinimumGrowint最小扩容数量(4)
GrowFactorint扩容因子(200%)

二、构造函数

1. 默认构造函数

public Queue()
{_array = Array.Empty<T>();
}
  • 初始化一个空队列,默认容量为 0,首次入队时自动扩容。

2. 指定初始容量的构造函数

public Queue(int capacity)
{if (capacity < 0)throw new ArgumentOutOfRangeException(...);_array = new T[capacity];
}
  • 指定初始容量,避免频繁扩容。

3. 使用 IEnumerable<T> 构造

public Queue(IEnumerable<T> collection)
{_array = EnumerableHelpers.ToArray(collection, out _size);if (_size != _array.Length) _tail = _size;
}
  • 将集合一次性入队,注意 _tail 的处理。

三、核心操作方法

1. 入队 Enqueue

public void Enqueue(T item)
{// 如果当前队列已满(元素数量等于数组长度),需要扩容if (_size == _array.Length){// 根据 GrowFactor(默认 200)计算新的容量,即当前容量的 200%// 例如:当前容量是 4,扩容后是 8;当前容量是 100,扩容后是 200int newcapacity = (int)((long)_array.Length * (long)GrowFactor / 100);// 如果计算出的新容量仍小于当前容量加上最小增长量(MinimumGrow = 4)// 则直接使用当前容量 + MinimumGrow,确保至少增加 4 个容量if (newcapacity < _array.Length + MinimumGrow){newcapacity = _array.Length + MinimumGrow;}// 调用 SetCapacity 方法,创建一个新的数组并复制旧数据SetCapacity(newcapacity);}// 将新元素插入到队尾位置_array[_tail] = item;// 移动队尾指针到下一个位置(考虑环形数组)// 例如:如果当前是数组末尾,则回到数组开头MoveNext(ref _tail);// 队列元素数量增加 1_size++;// 增加版本号,用于在枚举时检测集合是否被修改(防止 InvalidOperationException)_version++;
}
  • 如果数组已满,则调用 SetCapacity 进行扩容。
  • 插入到 _tail 位置,更新 _tail_size
扩容逻辑:
  • 默认扩容为原来的 200%(即翻倍),如果不够,则至少扩容 4 个单位。

2. 出队 Dequeue

public T Dequeue()
{if (_size == 0)ThrowForEmptyQueue();T removed = _array[_head];if (RuntimeHelpers.IsReferenceOrContainsReferences<T>()){_array[_head] = default!;}MoveNext(ref _head);_size--;_version++;return removed;
}
  • 如果队列为空抛出异常。
  • 获取 _head 处的元素并清除(引用类型时)。
  • 更新 _head_size

3. 尝试出队 TryDequeue

public bool TryDequeue([MaybeNullWhen(false)] out T result)
{if (_size == 0){result = default!;return false;}result = _array[_head];if (RuntimeHelpers.IsReferenceOrContainsReferences<T>()){_array[_head] = default!;}MoveNext(ref _head);_size--;_version++;return true;
}
  • 不抛出异常,返回 false 表示队列为空。

4. 查看队首 Peek

public T Peek()
{if (_size == 0)ThrowForEmptyQueue();return _array[_head];
}
  • 返回队首元素,不移除。

5. 尝试查看队首 TryPeek

public bool TryPeek([MaybeNullWhen(false)] out T result)
{if (_size == 0){result = default!;return false;}result = _array[_head];return true;
}
  • 类似 TryDequeue,不抛异常。

6. 清空队列 Clear

public void Clear()
{// 如果队列中有元素(_size > 0),才需要进行清理操作if (_size != 0){// 如果 T 是引用类型或包含引用类型(如类、字符串等),需要显式清空内存// 避免内存泄漏(例如引用未被释放,GC 无法回收)if (RuntimeHelpers.IsReferenceOrContainsReferences<T>()){// 情况 1:元素是连续存储的(_head < _tail)if (_head < _tail){// 清空从 _head 开始的 _size 个元素Array.Clear(_array, _head, _size);}else{// 情况 2:元素是环形存储的(_head > _tail)// 第一步:清空从 _head 到数组末尾的部分Array.Clear(_array, _head, _array.Length - _head);// 第二步:清空从数组开头到 _tail 的部分Array.Clear(_array, 0, _tail);}}// 将元素数量置为 0_size = 0;}// 重置队头和队尾指针为 0,表示队列为空_head = 0;_tail = 0;// 增加版本号,用于防止在枚举过程中修改队列导致异常(如 InvalidOperationException)_version++;
}

情况 1:连续存储

_array = [_, _, 1, 2, 3, _]
_head = 2
_tail = 5
_size = 3
  • _head < _tail,执行 Array.Clear(_array, 2, 3),清除 1, 2, 3
  • _size = 0
  • _head = 0, _tail = 0

情况 2:环形存储

_array = [4, _, _, _, 1, 2, 3]
_head = 4
_tail = 1
_size = 4
  • 执行两次 Array.Clear
    • Array.Clear(_array, 4, 3) → 清除 1, 2, 3
    • Array.Clear(_array, 0, 1) → 清除 4
  • _size = 0
  • _head = 0, _tail = 0

7. 遍历队列 GetEnumerator

public Enumerator GetEnumerator()
{return new Enumerator(this);
}
  • 返回一个 Enumerator,支持 foreach 遍历。
  • 内部使用 _version 检测是否在遍历时被修改。

8. 转换为数组 ToArray

public T[] ToArray()
{// 创建一个与当前队列大小相同的新数组T[] arr = new T[_size];// 判断队列中元素的存储方式(连续 or 环形)// 情况 1:队列元素是连续存储的(_head <= _tail)if (_head < _tail){// 将数组中从 _head 开始的 _size 个元素复制到新数组的起始位置Array.Copy(_array, _head, arr, 0, _size);}else{// 情况 2:队列元素是环形存储的(_head > _tail)// 即:队列数据从 _head 到数组末尾,再从数组开头到 _tail// 第一步:复制从 _head 到数组末尾的部分到新数组的起始位置Array.Copy(_array, _head, arr, 0, _array.Length - _head);// 第二步:复制从数组开头到 _tail 的部分到新数组后续位置Array.Copy(_array, 0, arr, _array.Length - _head, _tail);}// 返回包含队列所有元素的新数组return arr;
}

情况 1:连续存储

_array = [_, _, 1, 2, 3, _]
_head = 2
_tail = 5
_size = 3
  • Array.Copy(_array, 2, arr, 0, 3) → 复制 1, 2, 3
  • 返回 [1, 2, 3]

情况 2:环形存储

_array = [4, _, _, _, 1, 2, 3]
_head = 4
_tail = 1
_size = 4
  • 执行两次 Array.Copy
    • Array.Copy(_array, 4, arr, 0, 3) → 复制 1, 2, 3
    • Array.Copy(_array, 0, arr, 3, 1) → 复制 4 到 3 的后面
  • 返回 [1, 2, 3, 4]


9. 扩容方法 SetCapacity

private void SetCapacity(int capacity)
{// 创建一个新的数组,容量为传入的 capacityT[] newarray = new T[capacity];// 如果当前队列中有元素(_size > 0),则需要复制旧数据到新数组if (_size > 0){// 情况 1:队列中元素是连续存储的(没有环绕)// 即:队头索引 < 队尾索引,元素从 _head 到 _tail - 1 连续存放if (_head < _tail){// 将旧数组中从 _head 开始的 _size 个元素复制到新数组的起始位置Array.Copy(_array, _head, newarray, 0, _size);}else{// 情况 2:队列中元素是环绕存储的(即队尾索引小于队头索引)// 例如:数组最后几个元素是队列的一部分,队头在数组末尾附近,队尾在数组开头附近// 第一步:复制从 _head 到数组末尾的部分Array.Copy(_array, _head, newarray, 0, _array.Length - _head);// 第二步:复制从数组开头到 _tail 的部分Array.Copy(_array, 0, newarray, _array.Length - _head, _tail);}}// 将旧数组替换为新数组_array = newarray;// 重置队头为新数组的起始位置(0)_head = 0;// 设置队尾位置:// 如果队列已满(_size == capacity),则队尾回到 0(表示队列已满)// 否则,队尾指向当前元素数量的位置(即下一个插入的位置)_tail = (_size == capacity) ? 0 : _size;// 增加版本号,用于防止在遍历时修改队列导致异常(如 InvalidOperationException)_version++;
}

情况 1:连续存储

_array = [_, _, 1, 2, 3, 4] (_head = 2, _tail = 6, _size = 4)
扩容到 capacity = 8复制后:
newarray = [1, 2, 3, 4, _, _, _, _]
_head = 0
_tail = 4

情况 2:环形存储

_array = [4, _, _, _, 1, 2, 3] (_head = 4, _tail = 1, _size = 4)
扩容到 capacity = 8复制后:
newarray = [1, 2, 3, 4, _, _, _, _]
_head = 0
_tail = 4

10. 指针移动 MoveNext

private void MoveNext(ref int index)
{int tmp = index + 1;if (tmp == _array.Length){tmp = 0;}index = tmp;
}
  • 实现环形数组的指针移动。

四、其他辅助方法

1. Contains

public bool Contains(T item)
{if (_size == 0)return false;if (_head < _tail)return Array.IndexOf(_array, item, _head, _size) >= 0;elsereturnArray.IndexOf(_array, item, _head, _array.Length - _head) >= 0 ||Array.IndexOf(_array, item, 0, _tail) >= 0;
}
  • 判断队列中是否包含某个元素。
  • 分段查找环形数组中的内容。

2. CopyTo

public void CopyTo(T[] array, int arrayIndex)
{// 参数检查:目标数组不能为 nullif (array == null){throw new ArgumentNullException(nameof(array));}// 参数检查:arrayIndex 必须在 [0, array.Length] 范围内if (arrayIndex < 0 || arrayIndex > array.Length){throw new ArgumentOutOfRangeException(nameof(arrayIndex), arrayIndex, SR.ArgumentOutOfRange_Index);}// 获取目标数组的长度int arrayLen = array.Length;// 检查目标数组从 arrayIndex 开始是否有足够空间容纳 _size 个元素if (arrayLen - arrayIndex < _size){throw new ArgumentException(SR.Argument_InvalidOffLen);}// 要复制的元素数量,初始为整个队列的大小int numToCopy = _size;// 如果队列为空,直接返回if (numToCopy == 0)return;// 第一部分复制:从 _head 开始,到数组末尾为止,最多能复制多少个元素int firstPart = Math.Min(_array.Length - _head, numToCopy);// 复制第一部分:从 _head 到数组末尾Array.Copy(_array, _head, array, arrayIndex, firstPart);// 减去已复制的元素数量numToCopy -= firstPart;// 如果还有剩余元素,说明队列是环形存储,需要复制第二部分(从数组开头到 _tail)if (numToCopy > 0){// 继续复制剩余元素,从数组开头开始复制Array.Copy(_array, 0, array, arrayIndex + firstPart, numToCopy);}
}

复制原理和SetCapacity方法相同


五、迭代器

// 实现 Queue<T> 的枚举器(迭代器),用于支持 foreach 循环。
// 枚举器使用队列内部的版本号(_version)来确保在枚举期间队列没有被修改。
public struct Enumerator : IEnumerator<T>, System.Collections.IEnumerator
{// 引用当前队列private readonly Queue<T> _q;// 保存队列的版本号,用于检测在枚举过程中队列是否被修改private readonly int _version;// 当前枚举索引:// -1 表示未开始// -2 表示已结束或已释放private int _index;// 当前枚举的元素private T? _currentElement;// 构造函数:初始化枚举器internal Enumerator(Queue<T> q){_q = q;_version = q._version; // 保存队列当前版本号_index = -1;           // 初始状态为未开始_currentElement = default;}// 清理资源,标记枚举结束public void Dispose(){_index = -2;           // 标记为已释放_currentElement = default;}// 移动到下一个元素public bool MoveNext(){// 检查队列是否被修改,若版本号不一致,抛出异常if (_version != _q._version)throw new InvalidOperationException(SR.InvalidOperation_EnumFailedVersion);// 如果枚举已结束,直接返回 falseif (_index == -2)return false;// 索引递增_index++;// 如果索引超出队列大小,说明已经枚举完所有元素if (_index == _q._size){_index = -2;           // 标记为结束_currentElement = default;return false;}// 获取队列的内部数组和容量T[] array = _q._array;int capacity = array.Length;// 计算实际数组索引:// 队列的元素不一定从数组索引 0 开始,并且可能是环形存储的。int arrayIndex = _q._head + _index;// 如果索引越界(环形),手动回绕(比取模更快)if (arrayIndex >= capacity){arrayIndex -= capacity;}// 保存当前元素_currentElement = array[arrayIndex];return true;}// 获取当前元素(泛型版本)public T Current{get{if (_index < 0)ThrowEnumerationNotStartedOrEnded();return _currentElement!;}}// 抛出异常:枚举未开始或已结束private void ThrowEnumerationNotStartedOrEnded(){Debug.Assert(_index == -1 || _index == -2);throw new InvalidOperationException(_index == -1 ? SR.InvalidOperation_EnumNotStarted : SR.InvalidOperation_EnumEnded);}// 获取当前元素(非泛型版本)object? IEnumerator.Current{get { return Current; }}// 重置枚举器(不推荐使用,因为 IEnumerator.Reset() 是显式接口实现)void IEnumerator.Reset(){if (_version != _q._version)throw new InvalidOperationException(SR.InvalidOperation_EnumFailedVersion);_index = -1;           // 重置为未开始状态_currentElement = default;}
}

设计亮点总结

功能说明
版本号检测使用 _version 检测队列是否在枚举期间被修改,防止并发修改异常
环形数组支持正确处理 _head_index 的组合,支持非连续存储的队列
性能优化使用 arrayIndex -= capacity 替代 % 运算符,提升性能
状态管理使用 _index 的状态区分枚举是否开始、进行中或已结束
线程安全限制不是线程安全的,但能检测到队列被修改
资源释放提供 Dispose() 方法清理资源,符合 .NET 枚举器规范

Queue<T>.Enumerator 是一个支持环形数组结构的枚举器,通过保存队列的版本号来检测并发修改,确保枚举过程的稳定性,同时使用高效的索引计算方式处理非连续存储的数据。


七、结语

Queue<T> 是 .NET 中一个非常实用的泛型集合类,其基于环形数组的实现方式在性能和内存管理上做了很好的权衡。

本文来自互联网用户投稿,该文观点仅代表作者本人,不代表本站立场。本站仅提供信息存储空间服务,不拥有所有权,不承担相关法律责任。
如若转载,请注明出处:http://www.pswp.cn/pingmian/93495.shtml
繁体地址,请注明出处:http://hk.pswp.cn/pingmian/93495.shtml
英文地址,请注明出处:http://en.pswp.cn/pingmian/93495.shtml

如若内容造成侵权/违法违规/事实不符,请联系英文站点网进行投诉反馈email:809451989@qq.com,一经查实,立即删除!

相关文章

微服务之注册中心与ShardingSphere关于分库分表的那些事

小伙伴们&#xff0c;你们好呀&#xff01;我是老寇&#xff01;跟我一起学习注册中心与ShardingSphere怎么一起使用 使用 nacos-shardingsphere例子&#xff0c;请点击我 注意&#xff1a;需要自己提前创建数据库和表 create database kcloud_platform_test;DROP TABLE IF…

python遇到异常流程

在 Python 中&#xff0c;程序遇到异常时的退出行为取决于是否对异常进行了捕获和处理&#xff1a;未捕获的异常&#xff1a; 如果异常发生后没有被 try-except 语句捕获&#xff0c;程序会立即终止&#xff0c;并返回一个非零的退出码&#xff08;通常是 1&#xff09;&#x…

【开源大模型和闭源大模型分别有哪些?两者的对比?部署私有化模型的必要性有哪些?】

以下是关于开源与闭源大模型的详细对比及私有化部署必要性的分析&#xff0c;结合最新行业动态和技术趋势&#xff1a;一、开源 vs 闭源大模型代表列表 1. 开源大模型&#xff08;2024年主流&#xff09;模型名称参数量机构特点LLaMA-38B-70BMeta商业使用需授权&#xff0c;多语…

SpringBoot--JWT

一、JWT 的简单了解1. 什么是 JWT&#xff1f;JWT&#xff08;JSON Web Token&#xff09;是一种开放标准&#xff08;RFC 7519&#xff09;&#xff0c;用于在 各方之间安全地传输信息。它基于 JSON 格式&#xff0c;信息通过 数字签名 的方式保证不可篡改&#xff0c;常用于 …

OpenTelemetry、Jaeger 与 Zipkin:分布式链路追踪方案对比与实践

OpenTelemetry、Jaeger 与 Zipkin&#xff1a;分布式链路追踪方案对比与实践 问题背景介绍 随着微服务架构的普及&#xff0c;服务之间调用链路变得异常复杂&#xff0c;单一服务故障或性能瓶颈往往牵一发动全身。分布式链路追踪&#xff08;Distributed Tracing&#xff09;能…

云原生俱乐部-RH124知识点总结(1)

RH124内容不是很多&#xff0c;但是也不知道多少能够写完&#xff0c;细节性的东西不会太多&#xff0c;但是确保每个都能够有印象能理解。本来是打算一篇文章写完的&#xff0c;但最后还是决定写一个系列。至于RH124和RH134的内容为什么放在了k8s系列的后面&#xff0c;那只是…

Redis面试精讲 Day 25:Redis实现分布式Session与购物车

【Redis面试精讲 Day 25】Redis实现分布式Session与购物车 在高并发、多节点的现代Web应用架构中&#xff0c;传统的本地Session存储方式已无法满足分布式系统的需求。如何实现跨服务、高可用、低延迟的用户状态管理&#xff0c;成为后端开发和面试中的高频考点。今天是“Redi…

本地文件上传到gitee仓库的详细步骤

本地文件上传到gitee仓库的详细步骤 &#x1f530; 一、前期准备 注册 Gitee 账号 访问 Gitee 官网完成注册并登录。 网址&#xff1a;https://gitee.com/ 安装 Git 下载 Git 官方客户端并完成安装。 下载网址&#xff1a;https://git-scm.com/downloads 配置 Git 全局信息&…

7 索引的监控

1. 查看索引的监控状态 GET /_cat/indices/log2?v&formatjson[{"health" : "yellow","status" : "open","index" : "log2","uuid" : "1OnzbVbJRn2grc5k198LlA","pri" : "…

【秋招笔试】2025.08.10米哈游秋招机考真题

📌 点击直达笔试专栏 👉《大厂笔试突围》 💻 春秋招笔试突围在线OJ 👉 笔试突围在线刷题 bishipass.com 米哈游 题目一:图书馆整理计划 1️⃣:贪心策略从左到右固定每个位置的最优元素 2️⃣:使用线段树维护区间最小值信息,支持单点更新和区间查询 3️⃣:每次选…

恒创科技:日本服务器 ping 不通?从排查到解决的实用指南

玩游戏、做跨境业务时&#xff0c;突然发现日本服务器 ping 不通&#xff0c;简直能让人瞬间焦虑 —— 这到底是网络崩了&#xff0c;还是服务器出问题了?在本文中&#xff0c;我们将探讨如何排除日本服务器 ping 请求故障&#xff0c;附带常见原因及解决办法。先搞清楚&#…

ThinkPHP的Controller获取request对象的几种方式

文章目录环境在Controller中获取Request对象构造器注入操作方法注入继承BaseController助手函数Facade参考环境 Windows 11 专业版XAMPP 8.2.12 PHP 8.2.12VSCode 1.103.0 在Controller中获取Request对象 要想在Controller中获取Request对象&#xff0c;有以下几种方式&…

week2-[循环结构]找出正数

week2-[循环结构]找出正数 题目描述 给定 NNN 个整数A1,A2,…,ANA_1,A_2,\ldots,A_NA1​,A2​,…,AN​。请求出这 NNN 个数中有多少个数是正数&#xff0c;并求出这些正数的平均值。如果 A1,A2,…,ANA_1,A_2,\ldots,A_NA1​,A2​,…,AN​ 不存在正数&#xff0c;那么输出 “Non…

Android平台RTSP播放器选型指南:从开源方案到跨平台低延迟专业SDK

1. 引言&#xff1a;Android RTSP 播放的三条路径 在 Android 平台实现 RTSP 播放&#xff0c;看似只是“能播起来”的问题&#xff0c;实际上是一个涉及延迟、稳定性、解码性能、协议兼容、工程可控性等多维指标的综合选型问题。 从安防监控、教育互动&#xff0c;到单兵指挥…

Linux安装及远程连接知识实践

文章目录一、VMware创建虚拟机故障及解决汇总1. 镜像下载2. 镜像选择安装3.安装VMware遇到的相关问题4. VMware操作系统的安装4.1 选择系统的引导4.2 修改网卡名为eth0的形式(和CentOS7以前保持一致)4.3 进入下一步安装界面4.4 进入到安装摘要页面(INSTALLATION SUMMARY)4.5 配…

F Core 批量写与“软实时”一致性:ExecuteUpdate / COPY / SqlBulkCopy 的取舍与事务权衡

EF Core 批量写与“软实时”一致性&#xff1a;ExecuteUpdate / COPY / SqlBulkCopy 的取舍与事务权衡 ✨ &#x1f4da; 目录EF Core 批量写与“软实时”一致性&#xff1a;ExecuteUpdate / COPY / SqlBulkCopy 的取舍与事务权衡 ✨1. 术语与目标 &#x1f9ed;2. 技术选型总览…

基于PSO粒子群多目标优化的微电网调度算法matlab仿真

目录 1.课题概述 2.系统仿真结果 3.核心程序 4.系统原理简介 4.1 改进粒子群算法 4.2 分布式电源与储能模型公式 4.3 多目标函数 5.参考文献 6.完整工程文件 1.课题概述 微电网优化调度的核心是在满足系统约束&#xff08;如功率平衡、设备出力限制等&#xff09;的前…

Spring AI ChatClient集成Deepseek

Spring AI ChatClient集成Deepseek 下文将简述如何通过spring ai集成deepseek实现智能对话。在开始之前你需要在deepseek官网申请一个apikey,并设置到系统变量中&#xff0c;保障安全性。 ChatModel 在集成deepseek前&#xff0c;我们先要了解一个chat model&#xff0c;chat m…

Azure微软云内网接入问题

1. 域名解析失败 azure需要给ClientSecretCredentialBuilder和AzureResourceManager都配置HTTP 代理,但还是会域名解析失败,netty会调用InetAddress.getByName解析域名.最终只能在hosts文件写死host和ip映射关系 2. netty版本不匹配,导致报错netty某个方法找不到 azure只用引入…

【IDEA】设置Debug调试时调试器不进入特定类(Spring框架、Mybatis框架)

问题 以Ruoyi-Vue项目为例&#xff0c;以Debug方式启动项目&#xff0c;在com.ruoyi.web.controller.system.SysUserController#list()方法中的userService.selectUserList(user)处打上断点&#xff0c;访问[系统管理–用户管理]页面&#xff0c;程序就会执行到该断点处此时按下…