04 Queue
Queue
0 Queue介绍
主要方法
Queue队列,在 JDK 中有两种不同类型的集合实现:单向队列(AbstractQueue) 和 双端队列(Deque)

Queue 中提供了两套增加、删除元素的 API,当插入或删除元素失败时,会有两种不同的失败处理策略。
| 方法及失败策略 | 插入方法 | 删除方法 | 查找方法 |
|---|---|---|---|
| 抛出异常 | add() | remove() | get() |
| 返回失败默认值 | offer() | poll() | peek() |
选取哪种方法的决定因素:插入和删除元素失败时,希望抛出异常还是返回布尔值
add() 和 offer() 对比:
在队列长度大小确定的场景下,队列放满元素后,添加下一个元素时,add() 会抛出 IllegalStateException异常,而 offer() 会返回 false 。
但是它们两个方法在插入某些不合法的元素时都会抛出三个相同的异常。

remove() 和 poll() 对比:
在队列为空的场景下, remove() 会抛出 NoSuchElementException异常,而 poll() 则返回 null 。
get()和peek()对比:
在队列为空的情况下,get()会抛出NoSuchElementException异常,而peek()则返回null。
Deque 接口
Deque 接口的实现非常好理解:从单向队列演变为双向队列,内部额外提供双向队列的操作方法即可:

Deque 接口额外提供了针对队列的头结点和尾结点操作的方法,而插入、删除方法同样也提供了两套不同的失败策略。除了add()和offer(),remove()和poll()以外,还有get()和peek()出现了不同的策略
AbstractQueue 抽象类
AbstractQueue 类中提供了各个 API 的基本实现,主要针对各个不同的处理策略给出基本的方法实现,定义在这里的作用是让子类根据其方法规范(操作失败时抛出异常还是返回默认值)实现具体的业务逻辑。

1 LinkedList :warning: 已废弃
继承关系

底层实现
LinkedList 底层采用双向链表数据结构存储元素,由于链表的内存地址非连续,所以它不具备随机访问的特点,但由于它利用指针连接各个元素,所以插入、删除元素只需要操作指针,不需要移动元素,故具有增删快、查询慢的特点。它也是一个非线程安全的集合。

由于以双向链表作为数据结构,它是线程不安全的集合;存储的每个节点称为一个Node,下图可以看到 Node 中保存了next和prev指针,item是该节点的值。在插入和删除时,时间复杂度都保持为 O(1)

关于 LinkedList,除了它是以链表实现的集合外,还有一些特殊的特性需要注意的。
- 优势:LinkedList 底层没有
扩容机制,使用双向链表存储元素,所以插入和删除元素效率较高,适用于频繁操作元素的场景 - 劣势:LinkedList 不具备
随机访问的特点,查找某个元素只能从head或tail指针一个一个比较,所以查找中间的元素时效率很低 - 查找优化:LinkedList 查找某个下标
index的元素时做了优化,若index > (size / 2),则从head往后查找,否则从tail开始往前查找,代码如下所示:
1 | LinkedList.Node<E> node(int index) { |
- 双端队列:使用双端链表实现,并且实现了
Deque接口,使得 LinkedList 可以用作双端队列。下图可以看到 Node 是集合中的元素,提供了前驱指针和后继指针,还提供了一系列操作头结点和尾结点的方法,具有双端队列的特性。

LinkedList 集合最让人树枝的是它的链表结构,但是我们同时也要注意它是一个双端队列型的集合。
1 | Deque<Object> deque = new LinkedList<>(); |
常用方法
1 | LinkedList() :初始化一个空的LinkedList实现。 |
LinkedList与ArrayList
- ArrayList是使用动态可调整大小的数组的概念实现的。 而LinkedList是双向链表实现。
- ArrayList允许随机访问其元素,而LinkedList则不允许。
- LinkedList还实现了Queue接口,该接口添加了比ArrayList更多的方法,例如offer(),peek(),poll()等。
- 与LinkedList相比, ArrayList添加和删除速度较慢,但在获取时却较快,因为如果LinkedList中的array已满,则无需调整数组大小并将内容复制到新数组。
- LinkedList比ArrayList具有更多的内存开销,因为在ArrayList中,每个索引仅保存实际对象,但是在LinkedList的情况下,每个节点都保存下一个和上一个节点的数据和地址。
2 ArrayDeque
使用数组实现的双端队列,它是无界的双端队列,最小的容量是8(JDK 1.8)。在 JDK 11 看到它默认容量已经是 16了。

ArrayDeque 在日常使用得不多,值得注意的是它与 LinkedList 的对比:LinkedList 采用链表实现双端队列,而 ArrayDeque 使用数组实现双端队列。
在文档中作者写到:ArrayDeque 作为栈时比 Stack 性能好,作为队列时比 LinkedList 性能好
由于双端队列只能在头部和尾部操作元素,所以删除元素和插入元素的时间复杂度大部分都稳定在 O(1) ,除非在扩容时会涉及到元素的批量复制操作。但是在大多数情况下,使用它时应该指定一个大概的数组长度,避免频繁的扩容。
3 PriorityQueue
底层原理
PriorityQueue 基于优先级堆实现的优先级队列,而堆是采用数组实现:

文档中的描述告诉我们:该数组中的元素通过传入 Comparator 进行定制排序,如果不传入Comparator时,则按照元素本身自然排序,但要求元素实现了Comparable接口,所以 PriorityQueue 不允许存储 NULL 元素。
PriorityQueue 应用场景:元素本身具有优先级,需要按照优先级处理元素
- 例如游戏中的VIP玩家与普通玩家,VIP 等级越高的玩家越先安排进入服务器玩耍,减少玩家流失。
1 | public static void main(String[] args) { |
执行上面的代码可以得到下面这种有趣的结果,可以看到氪金使人带来快乐。

VIP 等级越高(优先级越高)就越优先安排进入游戏(优先处理),类似这种有优先级的场景还有非常多,各位可以发挥自己的想象力。
PriorityQueue 总结:
PriorityQueue 是基于优先级堆实现的优先级队列,而堆是用数组维护的
PriorityQueue 适用于元素按优先级处理的业务场景,例如用户在请求人工客服需要排队时,根据用户的VIP等级进行
插队处理,等级越高,越先安排客服。
主要方法
1 | boolean add(object) :将指定的元素插入此优先级队列。 |









