Queue简介
实现了一个先进先出的集合,提供了在队列中进行元素的添加和移除的功能。底层实现依赖于循环数组。
Queue源码分析
1、基本结构
- _array:使用泛型数组存储数据
- _head:表示队列的头部索引(即下一个要出队的元素位置)
- _tail:表示队列的尾部索引(即下一个要入队的元素位置)
- _size:表示队列中当前元素的数量
- _version:用于跟踪队列的修改版本,以支持迭代器的正确操作
- _defaultCapacity:初始容量,默认为4
public class Queue<T> : IEnumerable<T>, ICollection, IReadOnlyCollection<T>
{
private T[] _array;
private int _head; // The index of the head of the queue.
private int _tail; // The index of the tail of the queue.
private int _size; // Number of elements.
private int _version;
private const int _defaultCapacity = 4;
public Queue()
{
_array = Array.Empty<T>();
}
public Queue(int capacity)
{
if (capacity < 0)
throw new ArgumentOutOfRangeException(nameof(capacity));
_array = new T[capacity];
}
public Queue(IEnumerable<T> collection)
{
if (collection == null)
throw new ArgumentNullException(nameof(collection));
_array = new T[_defaultCapacity];
foreach (var item in collection)
{
Enqueue(item);
}
}
}
2、动态扩展机制
- 当队列中元素数量达到当前数组的容量时,会自动扩展数组的大小,通常是将当前容量翻倍,以减少频繁的内存分配操作
public void Enqueue(T item)
{
if (_size == _array.Length)
{
// 计算新的容量,通常是当前容量的两倍
//在计算新容量时,直接使用 array.Length * 2 可能会导致溢出,尤其是当数组的长度接近 int.MaxValue 时。为了避免这种情况,使用 long 类型进行计算。
//通过 (long)array.Length * 200L / 100L,先将 array.Length 转换为 long,然后再进行乘法和除法运算,确保中间计算过程不会溢出。
int newCapacity = (int)((long)_array.Length * 200L / 100L);
// 确保新的容量至少比当前容量大 _defaultCapacity
if (newCapacity < _array.Length + _defaultCapacity)
{
newCapacity = _array.Length + _defaultCapacity;
}
SetCapacity(newCapacity);
}
_array[_tail] = item;
// 更新队列尾部索引,如果到达数组末尾则循环到数组开头
//循环数组用于实现固定大小的队列。它的一个关键特性是,当到达数组的末尾时,继续从数组的起始位置进行操作,从而实现逻辑上的连续存储。
_tail = (_tail + 1) % _array.Length;
_size++;
_version++;
}
private void SetCapacity(int capacity)
{
T[] newArray = new T[capacity];
if (_size > 0)
{
// 如果队列中的元素是连续存储的(即 head 在 tail 之前)
if (_head < _tail)
{
// 直接将从 head 到 tail 之间的元素复制到新数组中
Array.Copy(_array, _head, newArray, 0, _size);
}
else
{
// 如果队列中的元素是分段存储的(即 head 在 tail 之后)
// 先复制从 head 到数组末尾的元素
Array.Copy(_array, _head, newArray, 0, _array.Length - _head);
// 再复制从数组开始到 tail 的元素
Array.Copy(_array, 0, newArray, _array.Length - _head, _tail);
}
}
_array = newArray;
// 更新 head 为 0,因为新数组中的第一个元素应该在索引 0 位置
_head = 0;
// 更新 tail,如果队列已满则设置为 0,否则设置为元素数量 _size
_tail = (_size == capacity) ? 0 : _size;
}
3、队列的操作方法
-
Dequeue:移除并返回队列的头部元素。
public T Dequeue() { if (_size == 0) throw new InvalidOperationException("Queue empty"); T removed = _array[_head]; _array[_head] = default!; _head = (_head + 1) % _array.Length; _size--; _version++; return removed; }
-
Peek:返回队列的头部元素但不移除。
public T Peek() { if (_size == 0) throw new InvalidOperationException("Queue empty"); return _array[_head]; }
-
Contains:检查队列中是否包含指定元素
public bool Contains(T item) { int index = _head; int count = _size; EqualityComparer<T> c = EqualityComparer<T>.Default; while (count-- > 0) { if (item == null) { if (_array[index] == null) return true; } else if (_array[index] != null && c.Equals(_array[index], item)) { return true; } index = (index + 1) % _array.Length; } return false; }
-
Clear:清空队列中的所有元素。
public void Clear() { if (_size > 0) { if (_head < _tail) { Array.Clear(_array, _head, _size); } else { Array.Clear(_array, _head, _array.Length - _head); Array.Clear(_array, 0, _tail); } } _head = 0; _tail = 0; _size = 0; _version++; }
-
ToArray:将队列中的元素复制到一个新数组。
public T[] ToArray() { T[] arr = new T[_size]; if (_size == 0) return arr; if (_head < _tail) { Array.Copy(_array, _head, arr, 0, _size); } else { Array.Copy(_array, _head, arr, 0, _array.Length - _head); Array.Copy(_array, 0, arr, _array.Length - _head, _tail); } return arr; }