Java8 PriorityQueue 源码解析

PriorityQueue 优先级队列

前世今生

extends AbstractQueue
AbstractQueue extends AbstractCollection implements Queue

实现原理大白话

内部使用你所熟悉的数据结构最堆来实现,每次都是取堆顶的元素。至于堆怎么实现,其实很简单,就一个数组而已,这里就不讨论怎么实现堆了。默认是根据传入的对象进行比较建立初始堆的。比如说 String 实现了 Comparable 接口,里面就是根据这个进行排序建立初始堆的。

主要的构造函数

1
2
3
// 可以使用外排序 Comparator 来构建你自己的堆,默认为空
// initialCapacity 为初始堆大小,一般来说,为了避免扩容,或者空间浪费,我们要选择合适的值,默认值为 DEFAULT_INITIAL_CAPACITY = 11
public PriorityQueue(int initialCapacity, Comparator<? super E> comparator)

常用的方法

添加元素

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
public boolean add(E e) {
return offer(e);
}
// 调用的是offer
public boolean offer(E e) {
if (e == null)
throw new NullPointerException();
// 几乎所有的集合类都有这个,顾名思义,主要是记录修改次数,实际上是为了防止你在遍历的时候更改了数据,造成不一致,会抛出ConcurrentModificationException 异常,注意,这与并发没有多大联系
modCount++;
int i = size;
// 检查是否需要扩容,queue就是真实数据
if (i >= queue.length)
grow(i + 1);
size = i + 1;
if (i == 0)
queue[0] = e;
else
// 使用经典的 siftUp 上移最后添加的元素, 保证我们的堆还是有序的
siftUp(i, e);
return true;
}

删除元素

1
2
3
4
5
6
7
8
9
10
11
12
13
// 同样使用 siftDown,首先将最后一个元素移到堆顶,再调整堆即可
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;
}

获取堆顶元素

1
2
3
4
5
// 直接数组返回
// 也可以使用 element(),是 abstractQueue 的方法,调用的也是 peek() 再一次感受到多态对象的强大
public E peek() {
return (size == 0) ? null : (E) queue[0];
}

获取后一位置的元素

1
2
3
4
5
6
7
8
9
// 因为使用的是最堆的数据结构,所以只能遍历,效率较低
private int indexOf(Object o) {
if (o != null) {
for (int i = 0; i < size; i++)
if (o.equals(queue[i]))
return i;
}
return -1;
}

关于siftUp

1
2
3
4
5
6
7
// 如果没有外排序,则使用内排序
private void siftUp(int k, E x) {
if (comparator != null)
siftUpUsingComparator(k, x);
else
siftUpComparable(k, x);
}

简单看一个函数,复习一下堆

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
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;
}

你可能遇到的坑

如果你中途改变了数据,有关排序的字段,最小堆是不会自动重新构建,那么,这个优先级队列就会失效!!!!!!举个例子,你把一堆学生按身高放入优先级队列,突然,有几个学生的身高改变了,这个最小堆是不会重构的,这时候得不到你想要的值,那有没有重构的函数,抱歉,没有!heapify 是私有的函数,但是你可以通过 new PriorityQueue(queue),重新构建一个