PriorityQueue 详解:Java 中的优先级队列 PriorityQueue 是 Java 集合框架中基于二叉堆 实现的优先级队列,它能够保证每次取出的元素都是队列中优先级最高的(默认是自然排序的最小值)。与普通队列的 “先进先出” 不同,PriorityQueue 的元素顺序由优先级决定,适用于需要动态获取最值的场景(如任务调度、Top K 问题等)。
核心特性
底层结构 :基于二叉堆(完全二叉树),使用数组存储,空间效率高。
排序方式 :支持自然排序(元素实现 Comparable
)或定制排序(传入 Comparator
)。
无界队列 :容量可动态扩容(默认初始容量 11),理论上没有上限(受限于内存)。
时间复杂度 :插入(offer
)和删除(poll
)操作的时间复杂度为 O(log n)
,获取最值(peek
)为 O(1)
。
非线程安全 :多线程环境需使用 PriorityBlockingQueue
(并发版本)。
不允许 null 元素 :插入 null
会抛出 NullPointerException
。
底层数据结构:二叉堆 PriorityQueue 的核心是二叉堆,它是一种满足以下性质的完全二叉树:
结构性 :是一棵完全二叉树(除最后一层外,每一层都被填满;最后一层的节点靠左排列)。
堆序性:
小顶堆 (默认):每个节点的值 ≤ 其左右子节点的值(根节点是最小值)。
<pre class="mermaid"> graph TD
0((0)) --- 1((1)) --- 3((3))
1((1)) --- 4((4))
0((0)) --- 2((2))</pre>
大顶堆 :每个节点的值 ≥ 其左右子节点的值(根节点是最大值)。
<pre class="mermaid"> graph TD
0((4)) --- 1((3)) --- 3((1))
1((3)) --- 4((0))
0((4)) --- 2((2))</pre>
堆的数组表示 完全二叉树的特性使得堆可以用数组高效存储,父子节点的索引关系如下:
对于索引为i的节点:
左子节点索引:2*i + 1
右子节点索引:2*i + 2
父节点索引:(i - 1) >>> 1
(等价于 (i-1)/2
,无符号右移避免负数问题)
例如,小顶堆的数组表示:
1 2 3 4 5 6 7 数组:[1, 3, 2, 6, 5, 4] 对应堆结构: 1 / \ 3 2 / \ / 6 5 4
小顶堆与大顶堆
小顶堆 :根节点是最小值,每次 poll
操作取出最小值。
大顶堆 :根节点是最大值,需通过定制比较器实现(Comparator.reverseOrder()
)。
核心源码解析(JDK 8) 重要属性 1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 private static final int DEFAULT_INITIAL_CAPACITY = 11 ;transient Object[] queue;private int size = 0 ;private final Comparator<? super E> comparator;transient int modCount = 0 ;private static final int MAX_ARRAY_SIZE = Integer.MAX_VALUE - 8 ;
构造函数 PriorityQueue 提供多种构造函数,核心是初始化容量和比较器:
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 26 27 28 29 30 31 32 33 34 35 36 37 38 39 40 41 public PriorityQueue () { this (DEFAULT_INITIAL_CAPACITY, null ); } public PriorityQueue (int initialCapacity) { this (initialCapacity, null ); } public PriorityQueue (Comparator<? super E> comparator) { this (DEFAULT_INITIAL_CAPACITY, comparator); } public PriorityQueue (int initialCapacity, Comparator<? super E> comparator) { if (initialCapacity < 1 ) throw new IllegalArgumentException(); this .queue = new Object[initialCapacity]; this .comparator = comparator; } public PriorityQueue (Collection<? extends E> c) { if (c instanceof SortedSet<?>) { SortedSet<? extends E> ss = (SortedSet<? extends E>) c; this .comparator = (Comparator<? super E>) ss.comparator(); initElementsFromCollection(ss); } else if (c instanceof PriorityQueue<?>) { PriorityQueue<? extends E> pq = (PriorityQueue<? extends E>) c; this .comparator = (Comparator<? super E>) pq.comparator(); initFromPriorityQueue(pq); } else { this .comparator = null ; initFromCollection(c); } }
核心操作:插入(offer
) 插入元素时,需维持堆的性质(小顶堆或大顶堆),步骤如下:
扩容(若数组已满);
将元素插入数组末尾;
向上调整(siftUp
):将新元素与父节点比较,若不符合堆序则交换,直到满足堆序。
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 26 27 28 29 30 31 32 33 34 35 36 37 38 39 40 41 42 public boolean offer (E e) { if (e == null ) throw new NullPointerException(); modCount++; int i = size; if (i >= queue.length) grow(i + 1 ); size = i + 1 ; if (i == 0 ) queue[0 ] = e; else siftUp(i, e); return true ; } private void grow (int minCapacity) { int oldCapacity = queue.length; int newCapacity = oldCapacity + ((oldCapacity < 64 ) ? (oldCapacity + 2 ) : (oldCapacity >> 1 )); if (newCapacity - MAX_ARRAY_SIZE > 0 ) newCapacity = hugeCapacity(minCapacity); queue = Arrays.copyOf(queue, newCapacity); } private void siftUpComparable (int k, E x) { Comparable<? super E> key = (Comparable<? super E>) x; while (k > 0 ) { int parent = (k - 1 ) >>> 1 ; Object e = queue[parent]; if (key.compareTo((E) e) >= 0 ) break ; queue[k] = e; k = parent; } queue[k] = key; }
示例 :向小顶堆 [1,3,2]
插入元素 0
插入末尾:[1,3,2,0]
向上调整:
0
(索引 3)的父节点是 3
(索引 1),0 < 3
→ 交换 → [1,0,2,3]
0
(索引 1)的父节点是 1
(索引 0),0 < 1
→ 交换 → [0,1,2,3]
结果:[0,1,2,3]
(满足小顶堆)
核心操作:删除(poll
) 删除堆顶元素(优先级最高的元素),步骤如下:
取出堆顶元素;
将数组末尾元素移到堆顶;
向下调整(siftDown
):将堆顶元素与子节点比较,若不符合堆序则交换,直到满足堆序。
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 26 27 28 29 30 31 32 public E poll () { if (size == 0 ) return null ; int s = --size; modCount++; E result = (E) queue[0 ]; E x = (E) queue[s]; queue[s] = null ; if (s != 0 ) siftDown(0 , x); return result; } private void siftDownComparable (int k, E x) { Comparable<? super E> key = (Comparable<? super E>) x; int half = size >>> 1 ; while (k < half) { int child = (k << 1 ) + 1 ; Object c = queue[child]; int right = child + 1 ; if (right < size && ((Comparable<? super E>) c).compareTo((E) queue[right]) > 0 ) c = queue[child = right]; if (key.compareTo((E) c) <= 0 ) break ; queue[k] = c; k = child; } queue[k] = key; }
示例 :从小顶堆 [0,1,2,3]
中 poll
元素
取出堆顶 0
,末尾元素 3
移到堆顶 → [3,1,2,null]
向下调整:
3
(索引 0)的左右子节点为 1
(索引 1)和 2
(索引 2),选择较小的 1
→ 3 > 1
交换 → [1,3,2,null]
3
(索引 1)的左右子节点为 3
(索引 3,已空),停止调整。
结果:[1,3,2]
(满足小顶堆),返回 0
其他常用方法
peek()
:获取堆顶元素(不删除),直接返回 queue[0]
。
remove(Object o)
:删除指定元素,需先遍历数组找到索引,再通过 removeAt
方法删除并调整堆。
size()
:返回元素数量 size
。
使用示例 1. 自然排序(小顶堆) 1 2 3 4 5 6 7 8 9 PriorityQueue<Integer> pq = new PriorityQueue<>(); pq.offer(3 ); pq.offer(1 ); pq.offer(2 ); System.out.println(pq.peek()); while (!pq.isEmpty()) { System.out.print(pq.poll() + " " ); }
2. 定制排序(大顶堆) 1 2 3 4 5 6 7 8 9 10 PriorityQueue<Integer> maxHeap = new PriorityQueue<>(Comparator.reverseOrder()); maxHeap.offer(3 ); maxHeap.offer(1 ); maxHeap.offer(2 ); System.out.println(maxHeap.peek()); while (!maxHeap.isEmpty()) { System.out.print(maxHeap.poll() + " " ); }
3. 自定义对象排序 1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 26 class Student implements Comparable <Student > { String name; int score; public Student (String name, int score) { this .name = name; this .score = score; } @Override public int compareTo (Student o) { return Integer.compare(this .score, o.score); } } PriorityQueue<Student> students = new PriorityQueue<>(); students.offer(new Student("Alice" , 90 )); students.offer(new Student("Bob" , 80 )); students.offer(new Student("Charlie" , 95 )); while (!students.isEmpty()) { System.out.println(students.poll().name); }
注意事项
迭代顺序不保证有序 :PriorityQueue
的迭代器(iterator()
)不按优先级顺序遍历,若需有序遍历,需多次调用 poll()
方法。
扩容策略 :初始容量 11,当容量 <64 时每次扩容增加 old + 2
,否则增加 50%,避免频繁扩容。
与 TreeSet
的区别:
PriorityQueue
是队列,允许重复元素;TreeSet
是集合,不允许重复,且全量有序。
PriorityQueue
侧重动态获取最值;TreeSet
侧重范围查询和全量排序
v1.3.10