在 Java 日常开发和算法刷题中,ArrayDeque、LinkedList 和 PriorityQueue 是三个出场率极高的容器。它们都实现了 Queue 接口,但底层结构、支持的操作和适用场景却截然不同。本文将从方法 API、底层特性、横向对比到代码实战,为你彻底梳理这三个集合类的核心知识。
一、ArrayDeque ------ 循环数组实现的双端队列
官方推荐: 可用作 FIFO 队列、LIFO 栈、双端队列,禁止存放
null元素。
ArrayDeque 底层由循环数组实现,头尾操作均摊时间复杂度均为 O(1) ,性能优于 LinkedList。它是 Deque 接口的主要实现类,在需要队列或栈的场景中应优先选用。
1.1 双端队列常用 API
| 方法 | 作用 | 失败行为 |
|---|---|---|
addFirst(e) |
头部添加元素 | 容量满时抛异常 |
addLast(e) / add(e) |
尾部添加元素 | 容量满时抛异常 |
offerFirst(e) |
头部添加元素 | 返回 true / false |
offerLast(e) / offer(e) |
尾部添加元素 | 返回 true / false |
removeFirst() |
删除并返回头部元素 | 空队列抛异常 |
removeLast() |
删除并返回尾部元素 | 空队列抛异常 |
pollFirst() |
删除并返回头部元素 | 空队列返回 null |
pollLast() |
删除并返回尾部元素 | 空队列返回 null |
getFirst() |
查看头部元素(不删除) | 空队列抛异常 |
getLast() |
查看尾部元素(不删除) | 空队列抛异常 |
peekFirst() |
查看头部元素(不删除) | 空队列返回 null |
peekLast() |
查看尾部元素(不删除) | 空队列返回 null |
1.2 作为栈使用(官方推荐替代 Stack)
push(e)→ 等价于addFirst(e)------ 入栈pop()→ 等价于removeFirst()------ 出栈peek()→ 等价于peekFirst()------ 查看栈顶
1.3 关键特性
- 底层循环数组,自动扩容,头尾操作极快。
- 不支持按索引随机访问 (没有
get(index)方法)。 - 非线程安全,多线程并发需自行同步。
- 不允许存入
null元素。
1.4 代码示例
import java.util.ArrayDeque;
public class ArrayDequeDemo {
public static void main(String[] args) {
ArrayDeque<Integer> deque = new ArrayDeque<>();
// 1. 当作普通 FIFO 队列(先进先出)
deque.offer(1); // 队尾入队
deque.offer(2);
Integer head = deque.poll(); // 队头出队,空返回 null
Integer peek = deque.peek(); // 查看队头,不删除
// 2. 当作栈使用(后进先出,推荐替代 Stack)
deque.push(10); // 入栈
deque.push(20);
Integer top = deque.pop(); // 弹出栈顶,空抛异常
Integer stackTop = deque.peek(); // 查看栈顶
// 3. 双端队列:头尾自由操作(滑动窗口、单调队列核心)
deque.offerFirst(100); // 头部添加
deque.offerLast(200); // 尾部添加
deque.pollFirst(); // 头部删除
deque.pollLast(); // 尾部删除
}
}
适用场景: BFS 队列、单调栈、滑动窗口单调队列、任何需要纯队列或栈的场合。
二、LinkedList ------ 双向链表,一身三用
允许存放
null元素 ,同时实现了List和Deque接口,可用作列表、队列或双端队列。
LinkedList 底层为双向链表,在头尾节点的增删同样是 O(1),但由于需要维护额外节点对象,内存开销和缓存友好度均不如 ArrayDeque。
2.1 List 接口方法(体现链表特性)
add(int index, E element)------ 指定位置插入get(int index)------ 按索引获取元素,时间复杂度 O(n)remove(int index)------ 按索引删除
⚠️ 由于
get(i)需要遍历,不适合大量随机访问。
2.2 Deque / Queue 全套方法
LinkedList 实现了与 ArrayDeque 完全相同的方法签名:addFirst、addLast、offerFirst、offerLast、removeFirst、pollFirst、peekFirst、push、pop、peek 等等。
2.3 优缺点
- ✅ 优势:在 已经定位到节点 的前提下,中间插入/删除只涉及指针更改。
- ❌ 劣势:每个元素包装成
Node对象,内存占用大,CPU 缓存不友好;纯头尾操作性能弱于ArrayDeque。
2.4 代码示例
import java.util.LinkedList;
public class LinkedListDemo {
public static void main(String[] args) {
LinkedList<Integer> list = new LinkedList<>();
// 1. 当作 List 使用(支持索引,但 O(n))
list.add(1);
list.add(1, 5); // 在索引 1 处插入
int num = list.get(0); // 随机访问
// 2. 当作 FIFO 队列
list.offer(10);
list.poll();
list.peek();
// 3. 当作栈 / 双端队列(方法同 ArrayDeque)
list.push(100);
list.pop();
list.offerFirst(20);
list.pollLast();
}
}
适用场景: 需要同时满足 List 索引访问 + 队列/栈操作,或者频繁在链表中间位置增删元素的场景。
三、PriorityQueue ------ 优先级队列(二叉堆实现)
仅实现
Queue接口,不是双端队列! 不允许null,元素必须可比较,默认 最小堆。
PriorityQueue 底层基于数组实现的二叉堆,入队(offer)和出队(poll)的时间复杂度均为 O(log n)。它只能操作队首(堆顶),不能操作队尾。
3.1 核心 Queue 方法
| 方法 | 功能 |
|---|---|
offer(E e) |
加入元素,自动上浮调整堆,O(log n) |
poll() |
取出并移除堆顶元素(优先级最高),下沉调整,O(log n);空返回 null |
peek() |
查看堆顶元素,O(1);空返回 null |
add(E e) |
等价 offer,失败抛异常 |
remove() |
等价 poll,空队列抛异常 |
element() |
等价 peek,空队列抛异常 |
3.2 重要坑点
- 没有
addFirst、pollLast这类双端方法! - 直接迭代(
for-each)不保证有序 ,只有不断poll()才能按优先级顺序取出。 - 删除任意元素
remove(Object o)需要 O(n) 遍历查找。 - 默认是最小堆,构造大根堆需传入
Comparator.reverseOrder()。
3.3 代码示例
import java.util.PriorityQueue;
import java.util.Comparator;
public class PriorityQueueDemo {
public static void main(String[] args) {
// 1. 默认最小堆:堆顶是最小值
PriorityQueue<Integer> minHeap = new PriorityQueue<>();
minHeap.offer(5);
minHeap.offer(2);
minHeap.offer(8);
System.out.println(minHeap.peek()); // 输出 2
minHeap.poll(); // 移除堆顶
// 2. 最大堆写法
PriorityQueue<Integer> maxHeap = new PriorityQueue<>(Comparator.reverseOrder());
maxHeap.offer(3);
maxHeap.offer(9);
System.out.println(maxHeap.peek()); // 输出 9
// ❌ 错误:没有 offerFirst 方法!
// maxHeap.offerFirst(1);
}
}
适用场景: TopK 问题、多路归并、任务优先级调度、动态数据流求中位数等。
四、通用方法设计模式(Queue 接口规范)
所有队列实现都遵循同一套两套方法约定,记忆一组即可:
| 操作 | 失败时抛异常 | 失败时返回特殊值(推荐) |
|---|---|---|
| 插入 | add(e) |
offer(e) |
| 删除 | remove() |
poll() |
| 查看队首 | element() |
peek() |
五、横向关键对比
| 维度 | ArrayDeque | LinkedList | PriorityQueue |
|---|---|---|---|
| 底层结构 | 循环数组 | 双向链表 | 数组实现的二叉堆 |
| 实现接口 | Deque |
List & Deque |
Queue |
| 能否做栈 / 双端队列 | ✅ | ✅ | ❌ |
| 头尾增删 | O(1) 均摊,最快 | O(1),但常数大 | --- |
| 插入 / 删除 | --- | --- | O(log n) |
是否允许 null |
❌ | ✅ | ❌ |
| 有序规则 | 按插入顺序 | 按插入顺序 | 按优先级(堆序) |
| 随机索引访问 | ❌ | ✅ O(n) | ❌ |
六、场景选择速记口诀
- 滑动窗口、单调队列、普通栈与队列 → ArrayDeque
- 同时需要 List 索引访问和队列操作,或频繁中间插入删除 → LinkedList
- 前 K 大/小、任务调度、多路归并 → PriorityQueue
七、总结
Queue 的设计十分灵活,一个 Queue 接口背后却对应着多种截然不同的实现。理解它们的底层数据结构和性能边界,是写出高效、优雅代码的基础。希望本文的 API 速查、代码示例和场景对比能帮助你快速选择适合的容器,在开发与面试中游刃有余。