VB.net 2010 视频教程 VB.net 2010 视频教程 python基础视频教程
SQL Server 2008 视频教程 c#入门经典教程 Visual Basic从门到精通视频教程
当前位置:
首页 > 编程开发 > c#编程 >
  • 泛型集合自定义——C#关于类型安全的队列实现

第一部分:C#基础入门
泛型集合自定义——类型安全的队列实现
实例介绍
队列是先进先出(FIFO)的数据结构,广泛应用于任务调度、消息传递等场景。.NET框架内置了Queue泛型队列,但自定义实现能帮你深入理解泛型原理和队列的底层逻辑。本节通过自定义泛型队列,掌握泛型的类型安全特性、数组扩容vb.net教程C#教程python教程SQL教程access 2010教程
机制,以及队列核心操作的实现细节。
需求分析
自定义泛型队列需满足以下核心需求:
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);
	}
	}
	}
	}

逐行讲解

  1. 泛型类定义
    public class MyGenericQueue
    泛型类型参数保证类型安全,避免存储不同类型元素导致的运行时错误;
    无需装箱拆箱(值类型直接存储,引用类型存储地址),性能更优。

  2. 核心字段
    _items:底层存储数组,初始容量为4;
    _head:队首元素的索引(每次Dequeue后后移);
    _tail:下一个入队元素的位置索引(每次Enqueue后后移);
    _count:当前元素数量(直接返回,避免遍历数组);
    DefaultCapacity:默认初始容量(设为4平衡内存占用和扩容频率)。

  3. 构造函数
    无参构造函数:初始化数组为默认容量,重置_head、_tail、_count;
    带容量构造函数:检查容量合法性(不能小于1),初始化数组。

  4. 入队操作(Enqueue)
    扩容检查:若元素数量等于数组长度,调用Resize翻倍扩容;
    元素添加:将元素放入_tail位置,_tail后移,_count加1。

  5. 出队操作(Dequeue)
    空队列检查:为空则抛出异常;
    获取元素:取_head位置元素,释放引用(GC优化);
    索引更新:_head后移,_count减1;
    重置优化:若_head到数组末尾,重置为0(利用循环特性,避免空间浪费)。

  6. 扩容方法(Resize)
    容量翻倍:新容量为旧容量的2倍,摊还时间复杂度为O(1)(多数入队操作无需扩容);
    元素复制:处理两种情况:
    未跨数组末尾:直接复制_head到_head+_count的元素;
    跨末尾:分两部分复制(从_head到数组末尾,再从0到剩余元素);
    重置索引:_head设为0,_tail设为_count(新数组从0开始存储)。

  7. 测试程序
    覆盖初始化、入队、扩容、出队、查看队首、空队列异常等场景;
    验证队列的FIFO特性和扩容逻辑是否正确。
    基础知识拓展

  8. 泛型的核心优势
    | 优势 | 说明 |
    | ---- | ---- |
    | 类型安全 | 编译时检查类型,避免运行时类型转换错误; |
    | 性能优化 | 无需装箱拆箱(值类型直接存储),减少内存开销; |
    | 代码复用 | 同一队列类可存储任意类型(如MyGenericQueue、MyGenericQueue); |

  9. 队列实现方式对比

实现方式 优点 缺点
数组 随机访问快,扩容后性能稳定; 扩容有开销,空间利用率可能低(循环数组可优化);
链表 无扩容开销,空间利用率100%; 随机访问慢,每个元素需额外存储指针;
  1. 循环数组优化
    本节实现已具备简单循环特性(_head重置为0);
    完整循环队列:_tail到达数组末尾时重置为0,利用已释放的空间(需标记队列满的条件:(_tail+1)%capacity == _head);
    .NET内置Queue采用循环数组实现,空间利用率更高。
  2. 内置Queue vs 自定义队列
    内置Queue:支持循环数组、更完善的异常处理、线程安全变体(ConcurrentQueue);
    自定义队列:适合学习泛型和数据结构原理,可根据业务需求扩展(如添加Clear、Contains方法)。
  3. 应用场景
    任务调度:后台任务队列(如异步邮件发送);
    消息传递:生产者-消费者模式(如日志队列);
    缓存管理:FIFO缓存淘汰策略(如最近最少使用LRU的简化版)。
    总结
    自定义泛型队列让你深入理解了泛型的类型安全、数组扩容机制和队列的FIFO特性。虽然.NET内置了Queue,但掌握底层实现能帮你更好地选择数据结构,优化业务代码。
    核心要点:
    泛型是实现类型安全集合的关键;
    数组实现队列需注意扩容和循环优化;
    异常处理和性能优化(如GC引用释放)是工业级代码的必备要素。
    通过本节学习,你可以将泛型和数据结构知识应用到更复杂的场景(如自定义栈、链表等)。

本站原创,转载请注明出处:https://www.xin3721.com/ArticlecSharp/c49423.html


相关教程