-
泛型集合自定义——C#关于类型安全的队列实现
第一部分:C#基础入门
泛型集合自定义——类型安全的队列实现
实例介绍
队列是先进先出(FIFO)的数据结构,广泛应用于任务调度、消息传递等场景。.NET框架内置了Queue
机制,以及队列核心操作的实现细节。
需求分析
自定义泛型队列需满足以下核心需求:
1.类型安全:通过泛型约束元素类型,避免装箱拆箱和类型错误;
2.核心操作:
1.Enqueue:入队(添加元素到队尾);
2.Dequeue:出队(移除并返回队首元素);
3.Peek:查看队首元素(不修改队列);
4.IsEmpty:判断队列是否为空;
5.Count:获取元素数量;
3.容量管理:初始容量为4,元素满时自动翻倍扩容(保证操作效率);
4.异常处理:空队列执行Dequeue/Peek时抛出InvalidOperationException。
代码实现
csharp
using System;
using System.Collections.Generic;
namespace GenericCollectionsDemo
{
// 自定义泛型队列类
public class MyGenericQueue<T>
{
// 底层存储数组
private T[] _items;
// 队首元素索引
private int _head;
// 下一个入队元素的位置索引
private int _tail;
// 当前元素数量
private int _count;
// 默认初始容量
private const int DefaultCapacity = 4;
// 无参构造函数:使用默认容量初始化
public MyGenericQueue()
{
_items = new T[DefaultCapacity];
_head = 0;
_tail = 0;
_count = 0;
}
// 带初始容量的构造函数
public MyGenericQueue(int initialCapacity)
{
if (initialCapacity < 1)
throw new ArgumentOutOfRangeException(nameof(initialCapacity), "初始容量不能小于1");
_items = new T[initialCapacity];
_head = 0;
_tail = 0;
_count = 0;
}
// 入队操作
public void Enqueue(T item)
{
// 检查是否需要扩容
if (_count == _items.Length)
Resize();
// 添加元素到队尾
_items[_tail] = item;
_tail++;
_count++;
}
// 出队操作
public T Dequeue()
{
// 空队列抛出异常
if (IsEmpty)
throw new InvalidOperationException("队列已空,无法执行Dequeue操作");
// 获取队首元素
T item = _items[_head];
// 释放队首元素的引用(GC优化)
_items[_head] = default(T);
// 队首索引后移
_head++;
_count--;
// 优化:若队首到数组末尾,重置索引(避免空间浪费)
if (_head == _items.Length)
_head = 0;
return item;
}
// 查看队首元素
public T Peek()
{
if (IsEmpty)
throw new InvalidOperationException("队列已空,无法执行Peek操作");
return _items[_head];
}
// 是否为空
public bool IsEmpty => _count == 0;
// 元素数量
public int Count => _count;
// 扩容方法
private void Resize()
{
// 新容量为旧容量的2倍(摊还时间复杂度O(1))
int newCapacity = _items.Length * 2;
T[] newItems = new T[newCapacity];
// 复制旧数组中的有效元素到新数组
// 有效元素范围:_head到_head+_count-1(考虑_head可能不在0的情况)
if (_head + _count > _items.Length)
{
// 跨数组末尾的情况(循环队列特性)
int part1Length = _items.Length - _head;
Array.Copy(_items, _head, newItems, 0, part1Length);
Array.Copy(_items, 0, newItems, part1Length, _count - part1Length);
}
else
{
// 未跨末尾的情况
Array.Copy(_items, _head, newItems, 0, _count);
}
// 更新字段:新数组、重置head和tail
_items = newItems;
_head = 0;
_tail = _count;
}
}
// 测试程序
class Program
{
static void Main(string[] args)
{
// 1. 初始化队列
MyGenericQueue<string> queue = new MyGenericQueue<string>();
Console.WriteLine("初始化队列:IsEmpty={0}, Count={1}", queue.IsEmpty, queue.Count);
// 2. 入队操作
queue.Enqueue("任务1");
queue.Enqueue("任务2");
queue.Enqueue("任务3");
queue.Enqueue("任务4");
queue.Enqueue("任务5"); // 触发扩容(容量从4→8)
Console.WriteLine("
入队5个元素后:IsEmpty={0}, Count={1}", queue.IsEmpty, queue.Count);
// 3. 查看队首
Console.WriteLine("
队首元素:{0}", queue.Peek());
// 4. 出队操作
Console.WriteLine("
出队元素:{0}", queue.Dequeue());
Console.WriteLine("出队后Count={0}", queue.Count);
// 5. 循环出队
Console.WriteLine("
循环出队剩余元素:");
while (!queue.IsEmpty)
{
Console.WriteLine(queue.Dequeue());
}
// 6. 空队列操作(测试异常)
try
{
queue.Dequeue();
}
catch (InvalidOperationException ex)
{
Console.WriteLine("
空队列操作异常:{0}", ex.Message);
}
}
}
}
逐行讲解
-
泛型类定义
public class MyGenericQueue
泛型类型参数保证类型安全,避免存储不同类型元素导致的运行时错误;
无需装箱拆箱(值类型直接存储,引用类型存储地址),性能更优。 -
核心字段
_items:底层存储数组,初始容量为4;
_head:队首元素的索引(每次Dequeue后后移);
_tail:下一个入队元素的位置索引(每次Enqueue后后移);
_count:当前元素数量(直接返回,避免遍历数组);
DefaultCapacity:默认初始容量(设为4平衡内存占用和扩容频率)。 -
构造函数
无参构造函数:初始化数组为默认容量,重置_head、_tail、_count;
带容量构造函数:检查容量合法性(不能小于1),初始化数组。 -
入队操作(Enqueue)
扩容检查:若元素数量等于数组长度,调用Resize翻倍扩容;
元素添加:将元素放入_tail位置,_tail后移,_count加1。 -
出队操作(Dequeue)
空队列检查:为空则抛出异常;
获取元素:取_head位置元素,释放引用(GC优化);
索引更新:_head后移,_count减1;
重置优化:若_head到数组末尾,重置为0(利用循环特性,避免空间浪费)。 -
扩容方法(Resize)
容量翻倍:新容量为旧容量的2倍,摊还时间复杂度为O(1)(多数入队操作无需扩容);
元素复制:处理两种情况:
未跨数组末尾:直接复制_head到_head+_count的元素;
跨末尾:分两部分复制(从_head到数组末尾,再从0到剩余元素);
重置索引:_head设为0,_tail设为_count(新数组从0开始存储)。 -
测试程序
覆盖初始化、入队、扩容、出队、查看队首、空队列异常等场景;
验证队列的FIFO特性和扩容逻辑是否正确。
基础知识拓展 -
泛型的核心优势
| 优势 | 说明 |
| ---- | ---- |
| 类型安全 | 编译时检查类型,避免运行时类型转换错误; |
| 性能优化 | 无需装箱拆箱(值类型直接存储),减少内存开销; |
| 代码复用 | 同一队列类可存储任意类型(如MyGenericQueue、MyGenericQueue ); | -
队列实现方式对比
| 实现方式 | 优点 | 缺点 |
|---|---|---|
| 数组 | 随机访问快,扩容后性能稳定; | 扩容有开销,空间利用率可能低(循环数组可优化); |
| 链表 | 无扩容开销,空间利用率100%; | 随机访问慢,每个元素需额外存储指针; |
-
循环数组优化
本节实现已具备简单循环特性(_head重置为0);
完整循环队列:_tail到达数组末尾时重置为0,利用已释放的空间(需标记队列满的条件:(_tail+1)%capacity == _head);
.NET内置Queue采用循环数组实现,空间利用率更高。 -
内置Queue
vs 自定义队列
内置Queue:支持循环数组、更完善的异常处理、线程安全变体(ConcurrentQueue );
自定义队列:适合学习泛型和数据结构原理,可根据业务需求扩展(如添加Clear、Contains方法)。 -
应用场景
任务调度:后台任务队列(如异步邮件发送);
消息传递:生产者-消费者模式(如日志队列);
缓存管理:FIFO缓存淘汰策略(如最近最少使用LRU的简化版)。
总结
自定义泛型队列让你深入理解了泛型的类型安全、数组扩容机制和队列的FIFO特性。虽然.NET内置了Queue,但掌握底层实现能帮你更好地选择数据结构,优化业务代码。
核心要点:
泛型是实现类型安全集合的关键;
数组实现队列需注意扩容和循环优化;
异常处理和性能优化(如GC引用释放)是工业级代码的必备要素。
通过本节学习,你可以将泛型和数据结构知识应用到更复杂的场景(如自定义栈、链表等)。
本站原创,转载请注明出处:https://www.xin3721.com/ArticlecSharp/c49423.html










