队列的数据结构与Java实现
1. 队列的基本概念
队列是一种遵循"先进先出"(FIFO)原则的线性数据结构。它允许在队尾进行插入(enqueue)操作,在队头进行删除(dequeue)操作。
1.1 队列的常见操作
- enqueue(item): 将元素添加到队列尾部
- dequeue(): 移除并返回队列头部的元素
- peek(): 查看但不移除队列头部的元素
- isEmpty(): 检查队列是否为空
- isFull(): 检查队列是否已满(仅适用于固定大小队列)
2. Java中的队列实现
2.1 基于数组的实现
java
/**
* 基于循环数组实现的队列
* 优点:内存连续,访问速度快
* 缺点:固定大小,扩容成本高
*/
public class ArrayQueue {
private int maxSize; // 队列的最大容量
private int[] queueArray; // 存储队列元素的数组
private int front; // 队头指针,指向队列第一个元素
private int rear; // 队尾指针,指向队列最后一个元素
private int size; // 当前队列中的元素数量
public ArrayQueue(int size) {
this.maxSize = size;
this.queueArray = new int[maxSize];
this.front = 0;
this.rear = -1;
this.size = 0;
}
/**
* 入队操作 - 将新元素添加到队列尾部
* @param value 要添加的元素值
* @throws RuntimeException 如果队列已满则抛出异常
* 示例: queue.enqueue(5) 将5加入队列
*/
/**
* 入队操作 - 将新元素添加到队列尾部
* @param value 要添加的元素值
* @throws IllegalStateException 如果队列已满则抛出异常
* @see #isFull() 检查队列是否已满
* 示例: queue.enqueue(5) 将5加入队列
*/
public void enqueue(int value) {
if (isFull()) {
throw new IllegalStateException("Queue is full");
}
rear = (rear + 1) % maxSize;
queueArray[rear] = value;
size++;
}
/**
* 出队操作 - 移除并返回队列头部的元素
* @return 被移除的队头元素
* @throws RuntimeException 如果队列为空则抛出异常
* 示例: int first = queue.dequeue() 获取并移除第一个元素
*/
/**
* 出队操作 - 移除并返回队列头部的元素
* @return 被移除的队头元素
* @throws NoSuchElementException 如果队列为空则抛出异常
* @see #isEmpty() 检查队列是否为空
* 示例: int first = queue.dequeue() 获取并移除第一个元素
*/
public int dequeue() {
if (isEmpty()) {
throw new NoSuchElementException("Queue is empty");
}
int value = queueArray[front];
front = (front + 1) % maxSize;
size--;
return value;
}
/**
* 查看队头元素(不移除)
* @return 当前队头元素的值
* @throws RuntimeException 如果队列为空则抛出异常
* 示例: int first = queue.peek() 查看但不移除第一个元素
*/
public int peek() {
if (isEmpty()) {
throw new RuntimeException("Queue is empty");
}
return queueArray[front];
}
}
2.2 基于链表的实现
java
/**
* 基于链表实现的队列
* 优点:动态扩容,内存利用率高
* 缺点:内存不连续,访问速度稍慢
*/
public class LinkedQueue {
/**
* 链表节点类
*/
private static class Node {
int data; // 节点存储的数据
Node next; // 指向下一个节点的引用
Node(int data) {
this.data = data;
}
}
private Node front; // 队头指针,指向队列第一个节点
private Node rear; // 队尾指针,指向队列最后一个节点
/**
* 入队操作 - 将新元素添加到队列尾部
* @param data 要添加的元素值
* 实现原理:
* 1. 创建新节点
* 2. 如果队列为空,设置front和rear都指向新节点
* 3. 否则将新节点链接到rear节点后面,并更新rear指针
*/
public void enqueue(int data) {
Node newNode = new Node(data);
if (rear == null) {
front = rear = newNode;
} else {
rear.next = newNode;
rear = newNode;
}
}
/**
* 出队操作 - 移除并返回队列头部的元素
* @return 被移除的队头元素
* @throws RuntimeException 如果队列为空则抛出异常
* 示例: int first = queue.dequeue() 获取并移除第一个元素
*/
public int dequeue() {
if (isEmpty()) {
throw new RuntimeException("Queue is empty");
}
int data = front.data;
front = front.next;
if (front == null) {
rear = null;
}
return data;
}
/**
* 查看队头元素(不移除)
* @return 当前队头元素的值
* @throws RuntimeException 如果队列为空则抛出异常
* 示例: int first = queue.peek() 查看但不移除第一个元素
*/
public int peek() {
if (isEmpty()) {
throw new RuntimeException("Queue is empty");
}
return front.data;
}
}
2.3 双端队列实现
双端队列(Deque)是一种允许在两端进行插入和删除操作的队列。与普通队列不同,双端队列可以在队头和队尾进行操作。
数据结构原理
双端队列通常使用循环数组或链表实现。循环数组可以更高效地利用内存空间,而链表则更灵活。
实现示例
java
public class ArrayDeque {
private int[] elements; // 存储队列元素的数组
private int head; // 队列头部指针
private int tail; // 队列尾部指针
private int size; // 当前队列元素数量
private int capacity; // 队列容量
/**
* 构造函数 - 初始化双端队列
* @param capacity 队列容量
*/
public ArrayDeque(int capacity) {
this.capacity = capacity;
this.elements = new int[capacity];
this.head = 0; // 头部指针初始指向0
this.tail = 0; // 尾部指针初始指向0
this.size = 0; // 初始元素数量为0
}
/**
* 在队列头部添加元素
* @param value 要添加的元素值
* @throws RuntimeException 如果队列已满则抛出异常
* 实现原理: 头部指针向前移动一位(使用模运算处理循环数组边界)
*/
public void addFirst(int value) {
if (isFull()) {
throw new RuntimeException("Deque is full");
}
head = (head - 1 + capacity) % capacity; // 循环数组处理
elements[head] = value;
size++;
}
/**
* 在队列尾部添加元素
* @param value 要添加的元素值
* @throws RuntimeException 如果队列已满则抛出异常
* 实现原理: 尾部指针向后移动一位(使用模运算处理循环数组边界)
*/
public void addLast(int value) {
if (isFull()) {
throw new RuntimeException("Deque is full");
}
elements[tail] = value;
tail = (tail + 1) % capacity; // 循环数组处理
size++;
}
/**
* 移除并返回队列头部元素
* @return 被移除的队头元素
* @throws RuntimeException 如果队列为空则抛出异常
* 实现原理: 头部指针向后移动一位(使用模运算处理循环数组边界)
*/
public int removeFirst() {
if (isEmpty()) {
throw new RuntimeException("Deque is empty");
}
int value = elements[head];
head = (head + 1) % capacity; // 循环数组处理
size--;
return value;
}
/**
* 移除并返回队列尾部元素
* @return 被移除的队尾元素
* @throws RuntimeException 如果队列为空则抛出异常
* 实现原理: 尾部指针向前移动一位(使用模运算处理循环数组边界)
*/
public int removeLast() {
if (isEmpty()) {
throw new RuntimeException("Deque is empty");
}
tail = (tail - 1 + capacity) % capacity; // 循环数组处理
int value = elements[tail];
size--;
return value;
}
}
2.4 优先队列实现
优先队列是一种特殊的队列,其中每个元素都有一个优先级值。与普通队列不同,优先队列的出队顺序不是先进先出(FIFO),而是根据元素的优先级,优先级最高的元素最先出队。
数据结构原理
优先队列通常使用堆(Heap)数据结构实现。堆是一种完全二叉树,满足以下性质:
- 最大堆:每个节点的值都大于或等于其子节点的值
- 最小堆:每个节点的值都小于或等于其子节点的值
java
public class PriorityQueue {
private int[] heap; // 存储元素的数组
private int size; // 当前元素数量
private int capacity; // 最大容量
public PriorityQueue(int capacity) {
this.capacity = capacity;
this.heap = new int[capacity];
this.size = 0;
}
/**
* 入队操作 - 将元素插入到优先队列中
* @param value 要插入的元素值
* @throws RuntimeException 如果队列已满则抛出异常
* 实现原理:
* 1. 将新元素添加到队列末尾
* 2. 执行上浮操作(heapifyUp)调整堆结构
*/
public void enqueue(int value) {
if (isFull()) {
throw new RuntimeException("Priority queue is full");
}
heap[size] = value;
heapifyUp(size);
size++;
}
/**
* 出队操作 - 移除并返回优先级最高的元素(堆顶元素)
* @return 优先级最高的元素
* @throws RuntimeException 如果队列为空则抛出异常
* 实现原理:
* 1. 取出堆顶元素(最大值)
* 2. 将堆末尾元素移到堆顶
* 3. 执行下沉操作(heapifyDown)调整堆结构
*/
public int dequeue() {
if (isEmpty()) {
throw new RuntimeException("Priority queue is empty");
}
int max = heap[0];
heap[0] = heap[size - 1];
size--;
heapifyDown(0);
return max;
}
/**
* 上浮操作 - 从指定位置向上调整队列结构
* @param index 需要上浮的节点索引
* 实现原理:
* 1. 计算父节点位置
* 2. 如果当前节点值大于父节点值,则交换它们
* 3. 递归向上调整直到满足队列性质
*/
private void heapifyUp(int index) {
int parent = (index - 1) / 2;
if (index > 0 && heap[index] > heap[parent]) {
swap(index, parent);
heapifyUp(parent);
}
}
/**
* 下沉操作 - 从指定位置向下调整队列结构
* @param index 需要下沉的节点索引
* 实现原理:
* 1. 计算左右子节点位置
* 2. 找出当前节点、左子节点、右子节点中的最大值
* 3. 如果最大值不是当前节点,则交换它们
* 4. 递归向下调整直到满足队列性质
*/
private void heapifyDown(int index) {
int left = 2 * index + 1;
int right = 2 * index + 2;
int largest = index;
if (left < size && heap[left] > heap[largest]) {
largest = left;
}
if (right < size && heap[right] > heap[largest]) {
largest = right;
}
if (largest != index) {
swap(index, largest);
heapifyDown(largest);
}
}
}
3. 时间复杂度分析
-
基本操作时间复杂度:
- enqueue: O(1)
- dequeue: O(1)
- peek: O(1)
- isEmpty: O(1)
-
空间复杂度: O(n)
4. 线程安全考虑
-
非线程安全实现:
- 上述ArrayQueue和LinkedQueue都不是线程安全的
- 多线程环境下可能导致数据不一致
-
线程安全解决方案:
- 使用synchronized关键字修饰方法
- 使用java.util.concurrent包中的并发集合
- 推荐使用
ConcurrentLinkedQueue
作为线程安全队列实现
-
性能优化建议:
- 避免过度同步,尽量缩小同步块范围
- 考虑使用读写锁(ReentrantReadWriteLock)优化读多写少场景
- 对于高并发场景,优先考虑无锁实现(CAS)
- 合理设置队列容量,避免内存溢出
- 使用Disruptor框架替代队列实现超高性能场景
5. 工程实践建议
- 优先使用Java标准库中的
Queue
接口实现 - 常用实现类
ArrayDeque
- 基于数组的双端队列LinkedList
- 基于链表的队列ConcurrentLinkedQueue
- 基于链表的无界非阻塞队列LinkedBlockingQueue
- 基于链表的有界阻塞队列ArrayBlockingQueue
- 基于数组的有界阻塞队列PriorityBlockingQueue
- 无界优先级阻塞队列SynchronousQueue
- 不存储元素的阻塞队列
- 队列大小限制:
- 数组实现有固定容量限制
- 链表实现受限于内存大小
- 异常处理:
- 空队列出队操作应明确抛出异常
- 队列满情况处理
6. 性能对比测试
java
public class QueueBenchmark {
public static void main(String[] args) {
int size = 10000000;
// ArrayQueue测试
long start = System.currentTimeMillis();
ArrayQueue arrayQueue = new ArrayQueue(size);
for (int i = 0; i < size; i++) {
arrayQueue.enqueue(i);
}
for (int i = 0; i < size; i++) {
arrayQueue.dequeue();
}
System.out.println("ArrayQueue耗时: " + (System.currentTimeMillis() - start) + "ms");
// LinkedQueue测试
start = System.currentTimeMillis();
LinkedQueue linkedQueue = new LinkedQueue();
for (int i = 0; i < size; i++) {
linkedQueue.enqueue(i);
}
for (int i = 0; i < size; i++) {
linkedQueue.dequeue();
}
System.out.println("LinkedQueue耗时: " + (System.currentTimeMillis() - start) + "ms");
//双端队列测试
start = System.currentTimeMillis();
ArrayDeque arrayDeque = new ArrayDeque(size);
for (int i = 0; i < size; i++) {
arrayDeque.addFirst(i);
}
for (int i = 0; i < size; i++) {
arrayDeque.removeFirst();
}
System.out.println("ArrayDeque耗时: " + (System.currentTimeMillis() - start) + "ms");
// PriorityQueue测试
start = System.currentTimeMillis();
PriorityQueue priorityQueue = new PriorityQueue(size);
for (int i = 0; i < size; i++) {
priorityQueue.enqueue(i);
}
for (int i = 0; i < size; i++) {
priorityQueue.dequeue();
}
System.out.println("PriorityQueue耗时: " + (System.currentTimeMillis() - start) + "ms");
}
}
7. 队列的应用场景
- 任务调度
- 消息队列
- 广度优先搜索
- 打印任务管理
- 缓冲区实现
8. 完整代码
本文中的代码示例可以在我的GitHub仓库中找到:查看完整代码