在总集篇中我大概梳理了一下整个集合类的关系,这篇文章是对总集篇的扩展,本文将详细讨论PriorityQueue的实现原理。所有涉及到的源码都是基于JDK11。顺便插一句,大家要学就学新的嘛,毕竟11可是长期支持版本,可以用好几年的那种。
PriorityQueue简介
如果你看了前面Map的内容,你会发现之后简直简单好多。扯回正题,PriorityQueue中文优先级队列,其继承关系为Queue-AbstractQueue-PriorityQueue,其内部元素有序,一直弹出元素可以获得一个有序的元素序列,内部采用堆(数据结构中的堆结构,不是Java的堆概念)的方式组织元素,采用数组的方式存储元素,这些特点都和数据结构中堆的概念一致。很奇怪这里没有一个SortedQueue的接口,可能大佬们认为不需要吧。在PriorityQueue的继承树中,没有Deque接口,所以不支持双端访问,同样的PriorityQueue不支持随机访问。Queue相关实现类会提供两套API,一套抛出异常,一套返回特殊值。
PriorityQueue的类描述
public class PriorityQueue<E> extends AbstractQueue<E>
implements java.io.Serializable {
Queue中规定需要实现的功能:
// 添加元素
boolean add(E e);
boolean offer(E e);
// 删除元素
E remove();
E poll();
// 获取元素
E element();
E peek();
AbstractQueue实现的一些功能
// add是对offer的封装
public boolean add(E e) {
if (offer(e))
return true;
else
throw new IllegalStateException("Queue full");
}
// remove是对poll的封装
public E remove() {
E x = poll();
// set不允许null的原因
if (x != null)
return x;
else
throw new NoSuchElementException();
}
// element是对peek的封装
public E element() {
E x = peek();
if (x != null)
return x;
else
throw new NoSuchElementException();
}
PriorityQueue的成员变量
// 默认初始化容量
private static final int DEFAULT_INITIAL_CAPACITY = 11;
// 使用数组存储元素
transient Object[] queue; // non-private to simplify nested class access
int size;
// 若未指定,则采用自然顺序
private final Comparator<? super E> comparator;
// 作用原理请参考前面的文章
transient int modCount; // non-private to simplify nested class access
private static final int MAX_ARRAY_SIZE = Integer.MAX_VALUE - 8;
支撑PriorityQueue的内部结构
由于PriorityQueue没有实现其他接口,所以它只需要完成集合类要求的Iterator接口,提供并、串行迭代器即可,所以其内部类只有用于支撑串行迭代的Itr和支持并行迭代的PriorityQueueSpliterator。
串行迭代
// 串行迭代,串行迭代不保证迭代的顺序和排序顺序相同,原因在后面分析removeAt函数时解释
private final class Itr implements Iterator<E> {
/**
* Index (into queue array) of element to be returned by
* subsequent call to next.
*/
// 下一个元素在数组中的索引
private int cursor;
/**
* Index of element returned by most recent call to next,
* unless that element came from the forgetMeNot list.
* Set to -1 if element is deleted by a call to remove.
*/
// 当前元素在数组中的索引
private int lastRet = -1;
/**
* A queue of elements that were moved from the unvisited portion of
* the heap into the visited portion as a result of "unlucky" element
* removals during the iteration. (Unlucky element removals are those
* that require a siftup instead of a siftdown.) We must visit all of
* the elements in this list to complete the iteration. We do this
* after we've completed the "normal" iteration.
*
* We expect that most iterations, even those involving removals,
* will not need to store elements in this field.
*/
// removeAt函数会导致未被遍历的元素替换已被遍历的元素导致这部分元素无法再被遍历
// 所以用该结构来记录这些元素,保证这些元素可以被遍历到
private ArrayDeque<E> forgetMeNot;
/**
* Element returned by the most recent call to next iff that
* element was drawn from the forgetMeNot list.
*/
// 当前元素
private E lastRetElt;
/**
* The modCount value that the iterator believes that the backing
* Queue should have. If this expectation is violated, the iterator
* has detected concurrent modification.
*/
private int expectedModCount = modCount;
Itr() {} // prevent access constructor creation
public boolean hasNext() {
// forgetMeNot中有元素也表示没有被遍历完
return cursor < size ||
(forgetMeNot != null && !forgetMeNot.isEmpty());
}
public E next() {
if (expectedModCount != modCount)
throw new ConcurrentModificationException();
if (cursor < size)
// 注意++是先用再加,所以cursor永远指向下一个元素
return (E) queue[lastRet = cursor++];
// 同样的,不能忘了forgetMeNot也可以取元素
if (forgetMeNot != null) {
lastRet = -1;
lastRetElt = forgetMeNot.poll();
if (lastRetElt != null)
return lastRetElt;
}
throw new NoSuchElementException();
}
// 调用PriorityQueue的removeAt函数,这个函数后面将详细分析,如果该函数返回一个具体的值,则需要将该值放入forgetMeNot中
public void remove() {
if (expectedModCount != modCount)
throw new ConcurrentModificationException();
if (lastRet != -1) {
E moved = PriorityQueue.this.removeAt(lastRet);
lastRet = -1;
if (moved == null)
cursor--;
else {
if (forgetMeNot == null)
forgetMeNot = new ArrayDeque<>();
forgetMeNot.add(moved);
}
}
// 需要被删除的元素在forgetMeNot中的情况
else if (lastRetElt != null) {
PriorityQueue.this.removeEq(lastRetElt);
lastRetElt = null;
} else {
throw new IllegalStateException();
}
expectedModCount = modCount;
}
}
并行迭代
final class PriorityQueueSpliterator implements Spliterator<E> {
private int index; // current index, modified on advance/split
private int fence; // -1 until first use
private int expectedModCount; // initialized when fence set
/** Creates new spliterator covering the given range. */
PriorityQueueSpliterator(int origin, int fence, int expectedModCount) {
this.index = origin;
this.fence = fence;
this.expectedModCount = expectedModCount;
}
private int getFence() { // initialize fence to size on first use
int hi;
if ((hi = fence) < 0) {
expectedModCount = modCount;
hi = fence = size;
}
return hi;
}
// 数组结构,直接二分
public PriorityQueueSpliterator trySplit() {
int hi = getFence(), lo = index, mid = (lo + hi) >>> 1;
return (lo >= mid) ? null :
new PriorityQueueSpliterator(lo, index = mid, expectedModCount);
}
public void forEachRemaining(Consumer<? super E> action) {
if (action == null)
throw new NullPointerException();
if (fence < 0) { fence = size; expectedModCount = modCount; }
final Object[] es = queue;
int i, hi; E e;
for (i = index, index = hi = fence; i < hi; i++) {
if ((e = (E) es[i]) == null)
break; // must be CME
action.accept(e);
}
if (modCount != expectedModCount)
throw new ConcurrentModificationException();
}
public boolean tryAdvance(Consumer<? super E> action) {
if (action == null)
throw new NullPointerException();
if (fence < 0) { fence = size; expectedModCount = modCount; }
int i;
if ((i = index) < fence) {
index = i + 1;
E e;
if ((e = (E) queue[i]) == null
|| modCount != expectedModCount)
throw new ConcurrentModificationException();
action.accept(e);
return true;
}
return false;
}
public long estimateSize() {
return getFence() - index;
}
public int characteristics() {
return Spliterator.SIZED | Spliterator.SUBSIZED | Spliterator.NONNULL;
}
}
PriorityQueue重要的函数
removeAt函数
这个函数是一个包级可见的函数,也就是说它不是一个对用户开放的API,其实现如下:
E removeAt(int i) {
// assert i >= 0 && i < size;
final Object[] es = queue;
modCount++;
// 直接指向最后一个元素
int s = --size;
// 如果你删除的就是最后一个元素,那就很棒,直接删除就好
if (s == i) // removed last element
es[i] = null;
else {
// 否则,将删除元素与最后一个元素的值交换,删除最后一个元素,然后调整使其满足堆
E moved = (E) es[s];
es[s] = null;
// 先向下筛选,主要是寻找moved应该放置的位置
siftDown(i, moved);
// 如果筛选后i节点的值与最后一个节点的值相等,则还要向上筛选
if (es[i] == moved) {
// 向上筛选
siftUp(i, moved);
// 为遍历的节点放在了遍历过的节点位置,需要返回该节点,上层函数会将该节点加入forgetMeNot中
if (es[i] != moved)
return moved;
}
}
return null;
}
// 向下筛选
private void siftDown(int k, E x) {
if (comparator != null)
// 如果又比较器就用指定比较器比较
siftDownUsingComparator(k, x, queue, size, comparator);
else
// 如果没有就自然顺序比较
siftDownComparable(k, x, queue, size);
}
// 具体实现,修改了源码的一些变量名,便于理解
private static <T> void siftDownComparable(int k, T x, Object[] es, int n) {
// assert n > 0;
Comparable<? super T> key = (Comparable<? super T>)x;
// 堆的性值,叶子节点是不需要调整的,最后一个非叶节点就是n >>> 1
int half = n >>> 1; // loop while a non-leaf
// 如果k是非叶节点,则需要调整
while (k < half) {
// 获取左孩子
int lChild = (k << 1) + 1; // assume left child is least
// 先默认最小值是左孩子节点
Object c = es[lChild ];
// 获取右孩子
int rChild = lChild + 1;
// 如果右孩子存在且右孩子更小
if (rChild < n &&
((Comparable<? super T>) c).compareTo((T) es[rChild ]) > 0)
// 将右孩子节点的值赋给c
c = es[lChild = rChild ];
// k是指moved元素,如果k更小
if (key.compareTo((T) c) <= 0)
// 终止循环,说明当前节点就是moved应该存在的节点
break;
// 否则,将c的值赋值给当前节点
es[k] = c;
// 此时lChild表示上面找的的小的节点所在的位置,当前位置元素发生了变动
// 从此位置开始,进行下一次筛选
k = lChild ;
}
// 找到了位置,将key的值赋给该位置
es[k] = key;
}
// 实现原理同siftDownComparable
private static <T> void siftDownUsingComparator(
......
}
// 向上筛选
private void siftUp(int k, E x) {
if (comparator != null)
siftUpUsingComparator(k, x, queue, comparator);
else
siftUpComparable(k, x, queue);
}
// 实现原理同siftDownComparable
private static <T> void siftUpComparable(int k, T x, Object[] es) {
......
}
// 实现原理同siftDownComparable
private static <T> void siftUpUsingComparator(
......
}
下图延时了一个向下筛选后还需要向上筛选的情况:
于是从这里可以看出,PriorityQueue的迭代器不保证返回的元素的有序性。
add/offer函数
public boolean add(E e) {
return offer(e);
}
public boolean offer(E e) {
if (e == null)
throw new NullPointerException();
modCount++;
int i = size;
if (i >= queue.length)
// 扩容函数,如果容量小于64,就扩容成两倍,否则增长50%
grow(i + 1);
// 只需要向上筛选,因为新加入的元素按完全二叉树的方式排列,即在最后一层从左至右加入
siftUp(i, e);
size = i + 1;
return true;
}
peek/element函数
public E peek() {
return (E) queue[0];
}
remove/poll函数
// remove函数与规定有些区别,可以用来删除指定元素
public boolean remove(Object o) {
int i = indexOf(o);
if (i == -1)
return false;
else {
removeAt(i);
return true;
}
}
// 弹出并删除根节点元素
public E poll() {
final Object[] es;
final E result;
// 与删除的逻辑一致
if ((result = (E) ((es = queue)[0])) != null) {
modCount++;
final int n;
final E x = (E) es[(n = --size)];
es[n] = null;
if (n > 0) {
final Comparator<? super E> cmp;
if ((cmp = comparator) == null)
siftDownComparable(0, x, es, n);
else
siftDownUsingComparator(0, x, es, n, cmp);
}
}
return result;
}
PriorityQueue就介绍这么多,需要注意其迭代过程中如果对元素进行了操作,则不保证迭代顺序,以及其删除元素的规则。