PriorityQueue 默认是最小堆,无需Comparator;自定义对象需用Comparator实现最小堆,注意避免符号错误、溢出和null异常,且非线程安全。

PriorityQueue 默认就是最小堆,不需要额外操作
Java 的 PriorityQueue 默认底层使用最小堆(min-heap)实现:队首元素始终是优先级最高的最小元素(按自然顺序)。只要元素实现了 Comparable 接口(比如 Integer、String),直接 new 就行,不用写 Comparator。
常见误操作是以为必须手动传 Comparator 才能“变成”最小堆——其实恰恰相反:Comparator 通常用来**覆盖默认行为**,比如改成最大堆,或按自定义字段排序。
-
new PriorityQueue()→ 按自然顺序升序,等价于最小堆 -
new PriorityQueue(Collections.reverseOrder())→ 才是最大堆 - 对
int、long等基本类型包装类,自然顺序即数值升序,所以最小值自动在队首
用 Comparator 实现最小堆的典型场景:自定义对象排序
当存的是自定义类(如 Task、Node)时,PriorityQueue 不知道怎么比大小,这时才需要 Comparator。关键点在于:要让它表现成最小堆,compare(a, b) 必须返回负数当 a 应该排在 b 前面(即 a 更小)。
例如按 Node.val 构建最小堆:
PriorityQueue<node> pq = new PriorityQueue((a, b) -> Integer.compare(a.val, b.val));</node>
注意别写成 b.val - a.val(那是最大堆),也别漏掉 Integer.compare 直接减——可能整数溢出。
- 推荐用
Integer.compare(x, y)、Objects.compare(x, y, Comparator.naturalOrder())避免溢出和 null 安全问题 - 如果对象字段可能为 null,需在
Comparator中显式处理,否则抛NullPointerException - Lambda 写法简洁,但复杂逻辑建议提取为静态方法或用
Comparator.comparing()链式调用
Comparator 里写错符号会导致“假最小堆”
这是最常踩的坑:逻辑反了,结果看似能跑,但每次 poll() 出来的不是真最小值。典型错误包括:
- 把
a - b写成b - a→ 实际建成最大堆 - 在
compare()里混淆了“谁优先级高”和“谁数值大”——堆的“小”指优先级高,不一定是数值小(比如按截止时间排序时,“早”的 deadline 优先级更高) - 用
return a.val 忽略相等情况 → 缺少== 0分支,违反Comparator合约,可能导致重复元素丢失或排序异常
验证方式很简单:插入几个已知大小关系的元素,连续 poll(),看输出是否严格升序。
性能与线程安全要注意的边界
PriorityQueue 是非线程安全的,多线程并发 offer()/poll() 会出错。不要试图靠外部同步包裹它来“修复”,性能差且易漏锁;真需要并发,改用 PriorityBlockingQueue。
-
offer()和poll()时间复杂度都是 O(log n),内部用数组模拟完全二叉树,没有传统指针开销 - 不支持高效查找或删除中间元素——没有
remove(Object)的高效实现(它是 O(n) 遍历) - 如果需要动态修改堆中某个元素的优先级,
PriorityQueue无原生支持,得自己维护索引映射 + 重新插入,或换用第三方库(如 Apache Commons Heap)
最小堆逻辑本身不难,难的是在自定义排序、null 处理、溢出防护、并发场景下保持正确性——这些地方一松懈,bug 就藏得深。
Java免费学习笔记:立即使用
解锁 Java 大师之旅:从入门到精通的终极指南











