PriorityQueue简介
一、总体概述
java.util.PriorityQueue<E>
基于最小堆 实现的可伸缩队列,符合队列 FIFO 的“取元素”语义,但优先级由比较器决定。它实现 Queue<E>
、Serializable
,默认小顶堆(最小值最高优先),可通过自定义 Comparator
变为最大堆或任意优先策略。
二、底层实现原理
维度 | 说明 |
核心结构 | 二叉堆:用 Object[] queue 存储,根节点索引 0,i 的左右孩子为 2i+1 / 2i+2 |
比较逻辑 | - 若构造时传入 Comparator<? super E> ,永远用它比较 - 否则要求 E 实现 Comparable<E> ,按自然顺序 |
动态扩容 | 初始容量默认 11;插入时若满 → Arrays.copyOf 扩成 old + (old < 64 ? old + 2 : old >> 1) |
堆调节 | - 上浮:siftUp() —— 新元素插入末尾,沿父方向与更小者交换 - 下沉:siftDown() —— 弹出堆顶后把最后一个元素放根,自顶向下与更小孩子交换 |
稳定性 | 不保证稳定;相等优先级元素的顺序可能变化 |
三、复杂度分析
操作 | 时间复杂度 | 备注 |
offer/add | O(log n) | 上浮 |
poll/remove() | O(log n) | 下沉 |
peek | O(1) | 直接读根 |
contains / remove(Object) | O(n) | 线性扫描 |
迭代 | O(n) | 非堆序(不可排序遍历) |
空间 | O(n) | 数组持有全部元素 |
常数因子:比 TreeSet
/TreeMap
更低(无额外指针、旋转),因此在仅需堆顶最值时更快。
四、完整 API 速查
分类 | 常用方法 | 说明 |
构造 | PriorityQueue() / PriorityQueue(int cap) | 默认小顶堆 |
| PriorityQueue(Comparator) | 自定义优先级 |
| PriorityQueue(Collection<? extends E>) | 线性堆化 O(n) |
插入 | boolean add(E) (抛异常)
boolean offer(E) (返回 false) | 放入元素 |
访问 | E peek() | 查看堆顶,不删除 |
弹出 | E poll() / E remove() | 删除并返回堆顶
remove(Object) 删除任意元素 |
批量 | addAll(Collection) / clear() | |
属性 | size() / isEmpty() | |
遍历 | Iterator<E> | 任意顺序,Fail‑fast |
数组化 | toArray() / toArray(T[]) | |
五、注意事项 & 易错点
⚠️ 点 | 细节 |
null 禁止 | 插入 null 抛 NullPointerException (优先级无法比较) |
迭代顺序 | iterator() 不是 优先级顺;仅保证包含全部元素 |
相等元素 | 允许重复;比较器返回 0 则视为同优先级,不会去重 |
修改元素字段 | 若元素的 compareTo/compare 结果在队列中被修改 ⇒ 堆顺序被破坏,应 移除‑修改‑重插 |
contains/remove(Object) | 内部线性查找,O(n) ,大量调用需谨慎 |
容量扩容 | 自动扩容但可能多次 System.arraycopy ,若已知规模可预设容量 |
线程安全 | 非线程安全:多线程并发写需外部同步;在并发场景用 PriorityBlockingQueue (无界)或自己加锁 |
六、典型使用场景
场景 | 说明 |
最短路径 Dijkstra / A* | int[] {node, dist} 或自定义 Node,最小堆按距离 |
K 个有序数组归并 | 堆存每个数组当前头元素,弹出后推新元素 |
Top‑K | 维护大小 K 的“最大堆”或“最小堆” |
流式中位数 | 双堆:大顶堆 + 小顶堆 |
任务调度 | 根据时间戳 / 权重弹出下一个任务 |
多路归并排序 | 外排、磁盘归并 |
七、线程安全方案
方案 | 说明 |
Collections.synchronizedCollection(pq) | 简单互斥包装,粒度大 |
PriorityBlockingQueue | JDK 并发包,基于 无锁堆 + ReentrantLock 适用生产消费者 |
手动锁 | ReentrantLock / synchronized 包围关键操作 |
八、性能细节
- 批量构造更快:
new PriorityQueue<>(collection)
采用 Floyd 堆化,O(n)
;逐个 add
为 O(n log n)
。 - 最大堆技巧:
PriorityQueue<Integer> max = new PriorityQueue<>((a,b)->b-a);
。 - 数组缓存:JDK 17 开始
siftUp
/ siftDown
内联优化,实测百万级插入 ≈ 7–10 M ops/s(取决于对象大小)。 - 小顶堆变 K 小:当只关心 Top‑K 最大 元素时,用 固定容量最小堆,当大小>k 时 poll()。
九、示例代码
🔚 小结
PriorityQueue
= 最小(或自定义)堆 + 动态数组; - 提供 O(log n) 的入队/出队,O(1) 查看堆顶;
- 适用于 Top‑K、Dijkstra、K 路归并、流式统计 等;
- 非线程安全,并发场景选
PriorityBlockingQueue
或外部加锁; - 避免迭代器顺序误解、避免修改元素优先级后不重平衡、合理设置初始容量。