Java源码阅读PriorityQueue
1类签名与简介
public class PriorityQueue<E> extends AbstractQueue<E> implements java.io.Serializable
PriorityQueue是一个基于优先级堆的无限队列,无限的意思是说队列的容量可以自动扩容,最大容量为整形最大值。扩容规定如下当容量小于64时扩大为原来的2倍,否则扩大为原来的1.5倍(也就是容量增大50%)。
优先级队列不允许null元素,也不允许插入不可比较的对象。不可比较是指待插入对象的类没有实现Comparable接口,或者构造PriorityQueue对象是没有提供一个待插入对象的comparator比较器。强行插入不可比较对象会报ClassCastException异常。举个例子,现有一个Student类,只有以下两种情况才能将Student的对象放入PriorityQueue中:
(1)Student实现Comparable接口
(2)new PriorityQueue时提供一个Student的比较器:Queue<Student> students = new PriorityQueue<>(new MyComparator())
该队列的头部是相对于指定顺序的最小元素。 如果多个元素被绑定到最小值,那么头就是这些元素之一。
优先级堆(小顶堆)是用数组实现的,对于小顶堆的性质请执行查阅相关数据结构。入队出队的方法时间复杂度都是O(logn)。
请注意,此实现不同步。若想要在多线程并发,请使用线程安全的PriorityBlockingQueue
类。
2 入队
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) grow(i + 1); size = i + 1; if (i == 0) queue[0] = e; else siftUp(i, e); return true; }
add和offer都是入队实现,add其实就是调用offer方法实现的,所以两者本质上没有区别。
offer首先会判断插入元素是否为null,优先队列不允许插入null的元素。接下来判断队列是否满了,满了就扩容。如果插入的是队列第一个元素就直接放在数组0号位。否则就进行上移调整siftUp。上移调整是从堆的最后一个节点(也就是队尾)开始不断通过比较parent节点找到插入的位置。
private void siftUp(int k, E x) { if (comparator != null) siftUpUsingComparator(k, x); else siftUpComparable(k, x); } //实现Comparable接口的情况 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; } //提供比较器Comparator的情况 private void siftUpUsingComparator(int k, E x) { while (k > 0) { int parent = (k - 1) >>> 1; Object e = queue[parent]; if (comparator.compare(x, (E) e) >= 0) break; queue[k] = e; k = parent; } queue[k] = x; }
每次插入都进行siftUp保证了始终是一个小顶堆,不会因为新的节点破坏堆的结构。
siftUpComparable和siftUpUsingComparator过程是一样的,唯一不同的是不同的接口调用的不同比较方法。
3出队
出队是移除队列队头的元素,即数组的0号位存储的元素,也是堆结构的堆顶。
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; }
poll原理如下,把最后一个元素放到堆顶(数组0号位),然后从堆顶开始往下调整siftDown,直到满足最小堆为止。siftDown实现过程如下
private void siftDown(int k, E x) { if (comparator != null) siftDownUsingComparator(k, x); else siftDownComparable(k, x); } //内部排序接口调用siftDownComparable private void siftDownComparable(int k, E x) { Comparable<? super E> key = (Comparable<? super E>)x; int half = size >>> 1; // loop while a non-leaf while (k < half) { int child = (k << 1) + 1; // assume left child is least 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; } //有外部比较器调用siftDownUsingComparator private void siftDownUsingComparator(int k, 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 && comparator.compare((E) c, (E) queue[right]) > 0) c = queue[child = right]; if (comparator.compare(x, (E) c) <= 0) break; queue[k] = c; k = child; } queue[k] = x; }
4总结
关于本篇文章,个人感觉描述的不是很清楚。
因为觉得不适合花费其他篇幅去详细介绍“Comparator与Comparable”以及堆排序等相关知识。如果觉得有问题的话建议先了解一下这些相关的基础知识。