243. Java 集合 - 队列和堆栈建模
Java Collections Framework 提供了两个接口来建模队列(Queue)和堆栈(Stack):
Queue接口:用于建模队列(FIFO,先进先出)。Deque接口:用于建模双端队列(Double-Ended Queue)。你可以在队列的两端执行push、pop、poll和peek操作,因此它既可以作为队列也可以作为堆栈来使用。
🛠️ Deque 接口:队列与堆栈的结合
- 队列(
Queue)只允许从队尾插入元素,且从队头移除元素,遵循FIFO(先进先出) 原则。 - 双端队列(
Deque)则允许你从队列的两端插入和删除元素,因此可以兼具队列和堆栈的功能。Deque接口非常灵活,支持同时作为堆栈(LIFO)和队列(FIFO)使用。
Deque 示例:
import java.util.ArrayDeque;
import java.util.Deque;
public class DequeExample {
public static void main(String[] args) {
Deque<String> deque = new ArrayDeque<>();
// 从尾部插入元素(作为队列)
deque.offer("A");
deque.offer("B");
deque.offer("C");
System.out.println("Deque after offer: " + deque);
// 从头部插入元素(作为堆栈)
deque.push("D");
System.out.println("Deque after push: " + deque);
// 从头部弹出元素(作为堆栈)
System.out.println("Pop element from deque: " + deque.pop());
// 从队头移除元素(作为队列)
System.out.println("Poll element from deque: " + deque.poll());
System.out.println("Deque after poll: " + deque);
}
}
输出:
Deque after offer: [A, B, C]
Deque after push: [D, A, B, C]
Pop element from deque: D
Poll element from deque: A
Deque after poll: [B, C]
在这个例子中,offer() 方法模拟了队列的行为,push() 和 pop() 则模拟了堆栈的行为。
🔄 队列和堆栈的并发应用
在并发编程中,队列和堆栈经常用于任务调度、消息传递、资源共享等场景。Java 提供了 BlockingQueue、BlockingDeque 和 TransferQueue 接口,这些接口扩展了 Queue 和 Deque 接口,增加了在并发环境中的操作方法。
BlockingQueue:一个阻塞队列,能够在没有元素时阻塞消费者线程,或在队列满时阻塞生产者线程。BlockingDeque:扩展了BlockingQueue,允许在队列两端进行阻塞操作。TransferQueue:一个可以转移元素的队列,允许生产者和消费者之间交换元素。
这些接口为高效的并发编程提供了强大的支持,尤其是在多线程环境下。
⚠️ 队列操作中的边界情况
队列和堆栈在使用时可能会遇到以下两种常见的边界情况:
- 队列已满,无法插入更多元素:
- 如果队列已满,你可能会遇到无法插入新元素的情况。比如,使用容量有限的队列时,
offer()方法可能会返回false,表示队列无法接受新元素。
- 如果队列已满,你可能会遇到无法插入新元素的情况。比如,使用容量有限的队列时,
- 队列为空,无法移除元素:
- 如果队列为空,调用
pop()、poll()或peek()等方法时,可能会遇到返回空值或抛出异常的情况。 poll()方法返回null,表示队列为空;pop()方法会抛出NoSuchElementException。
- 如果队列为空,调用
示例:队列满时的行为
import java.util.LinkedList;
import java.util.Queue;
public class QueueOverflowExample {
public static void main(String[] args) {
// 创建容量为2的有界队列
Queue<String> queue = new ArrayBlockingQueue<>(2);
queue.offer("A");
queue.offer("B");
System.out.println("Queue after two offers: " + queue);
// 尝试添加第三个元素,会返回false
if (!queue.offer("C")) {
System.out.println("Queue is full, unable to add element 'C'.");
}
}
}
输出:
Queue after two offers: [A, B]
Queue is full, unable to add element 'C'.
在这个例子中,offer() 方法尝试在队列满时插入元素,结果返回 false,说明无法添加新元素。
🧠 思考:如何处理队列空或满的情况?
不同的队列实现可以有不同的策略来应对队列满或队列空的情况。对于某些队列实现,可以采用阻塞策略(如 BlockingQueue),让线程在无法执行操作时自动等待。而对于不支持阻塞操作的队列,可能会直接返回一个指示操作失败的值(如 null 或 false),或者抛出异常。
🚀 总结
Queue接口 主要用于建模先进先出(FIFO)行为的队列。Deque接口 是双端队列,允许在队列的两端执行插入和删除操作,既可以作为队列使用,也可以作为堆栈使用。- 队列和堆栈在并发编程中具有重要应用,Java 提供了扩展接口如
BlockingQueue和BlockingDeque来处理更复杂的并发问题。 - 在实际开发中,我们需要了解如何处理队列为空或满的边界情况,以保证程序的稳定性和性能。
🧐 互动问题
问题:如果你想在多线程环境下安全地操作队列,并且希望当队列为空时阻塞消费者线程,应该使用哪个接口?
答案:你应该使用 BlockingQueue 接口,它支持在队列为空时阻塞消费者线程,直到有元素可供消费。