//当我们修改PriorityQueue 类的时候,对应的SerialversionUID也变化了
//而序列化和反序列化就是通过对比其SerialversionUID来进行的,一旦SerialversionUID不匹配,反序列化就无法成功。故此我们需要自己设定SerialversionUID
private static final long serialVersionUID = -7720805057305804111L;
//初始容量
private static final int DEFAULT_INITIAL_CAPACITY = 11;
//数组最大容量
private static final int MAX_ARRAY_SIZE = Integer.MAX_VALUE - 8;
//存储元素的堆(数组),用transient关键字标记的成员变量不参与序列化过程
transient Object[] queue;
//队列中当前数组的大小
private int size = 0;
//比较器(两种方式:元素的自然顺序,或,指定比较器)
private final Comparator<? super E> comparator;
//修改次数
transient int modCount = 0;
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;
}
本队列可以实现存储前k个大的元素,其一重要的原因就是可以传入一个新的比较器并且重写它的compare()方法,实现自己的比较逻辑。从而根据实际场景实现队列的优先级。
//创造一个优先级队列,队列中包含一个具体集合中的所有元素
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);
}
}
//创建一个优先级队列,队列中包含一个PriorityQueue的所有元素
public PriorityQueue(PriorityQueue<? extends E> c) {
this.comparator = (Comparator<? super E>) c.comparator();
initFromPriorityQueue(c);
}
//创建一个优先级队列,队列中包含一个SortedSet的所有元素
public PriorityQueue(SortedSet<? extends E> c) {
this.comparator = (Comparator<? super E>) c.comparator();
initElementsFromCollection(c);
}
//初始化
private void initFromPriorityQueue(PriorityQueue<? extends E> c) {
//通过反射特性确定c的对象类型
if (c.getClass() == PriorityQueue.class) {
//若对象是PriorityQueue队列则进行如下操作
this.queue = c.toArray(); //将传入队列转化为数组
this.size = c.size(); //读取传入队列的大小
} else {
initFromCollection(c);
}
}
private void initElementsFromCollection(Collection<? extends E> c) {
Object[] a = c.toArray();
// If c.toArray incorrectly doesn't return Object[], copy it.
if (a.getClass() != Object[].class)
a = Arrays.copyOf(a, a.length, Object[].class);
int len = a.length;
if (len == 1 || this.comparator != null)
for (int i = 0; i < len; i++)
if (a[i] == null)
throw new NullPointerException();
this.queue = a;
this.size = a.length;
}
//使用给定集合中的元素初始化队列数组
private void initFromCollection(Collection<? extends E> c) {
initElementsFromCollection(c); //将集合中的元素填充到堆中
heapify(); //调整为最小堆
}
private void heapify() {
//(size >>> 1) - 1 是堆中的最大非叶子节点的下标
for (int i = (size >>> 1) - 1; i >= 0; i--)
siftDown(i, (E) queue[i]);
}
从左向右为队列[8,7,6,5,4,3,2,1]调用heapify
的流程示意图:
private static final int MAX_ARRAY_SIZE = Integer.MAX_VALUE - 8;
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)
//minCapacity = i + 1 , 进入扩容操作
grow(i + 1);
size = i + 1;
//首次添加元素e,则将e置于堆首
if (i == 0)
queue[0] = e;
else
//从下往上调整堆
siftUp(i, e);
return true;
}
//返回堆首元素
public E peek() {
return (size == 0) ? null : (E) queue[0];
}
public boolean remove(Object o) {
//获取对象o的下标
int i = indexOf(o);
if (i == -1)
return false;
else {
//移除下标为i的元素,并返回该值
removeAt(i);
return true;
}
}
private E removeAt(int i) {
// assert i >= 0 && i < size;
modCount++;
int s = --size;
if (s == i) // removed last element
queue[i] = null;
else {
E moved = (E) queue[s];
queue[s] = null;
siftDown(i, moved);
//如果堆调整完,queue[i]与moved相同,则向上调整堆
if (queue[i] == moved) {
siftUp(i, moved);
if (queue[i] != moved)
return moved;
}
}
return null;
}
private void grow(int minCapacity) {
int oldCapacity = queue.length;
// 如果旧容量小于64则新容量扩大两倍后+2
//否则则新容量扩大1.5倍
int newCapacity = oldCapacity + ((oldCapacity < 64) ?
(oldCapacity + 2) :
(oldCapacity >> 1));
// 溢出相关代码
if (newCapacity - MAX_ARRAY_SIZE > 0)
newCapacity = hugeCapacity(minCapacity);
//拷贝旧queue,创建容量为newCapacity的新queue,空位填充Null
queue = Arrays.copyOf(queue, newCapacity);
}
private static int hugeCapacity(int minCapacity) {
if (minCapacity < 0) // overflow
throw new OutOfMemoryError();
return (minCapacity > MAX_ARRAY_SIZE) ?
Integer.MAX_VALUE :
MAX_ARRAY_SIZE;
}
private void siftDown(int k, E x) {
if (comparator != null)
siftDownUsingComparator(k, x);
else
siftDownComparable(k, x);
}
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;
}
private void siftDownUsingComparator(int k, E x) {
int half = size >>> 1;
//寻找该节点x所处的正确结点的下标(通过交换)
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;
}
调用siftDown()
方法对堆进行调整,最后返回原来0下标处的那个元素(也就是最小的那个元素)。重点是siftDown(int k, E x)
方法,该方法的作用是从k指定的位置开始,将x
逐层向下与当前点的左右孩子中较小的那个交换,直到x
小于或等于左右孩子中的任何一个为止。
本实例实现了小顶堆的排序功能,即可以通过PriorityQueue
来选出前k
个最值(从小到大),并通过lamada表达式输出。另外,在自己实现的Comparator
比较器中:
compare
方法, return o2-o1
来实现大顶堆,即从大到小筛选public class PriorityQueueTest {
public static void main(String[] args) {
//创建一个降序排列的PriorityQueue,自定义比较器作为参数
PriorityQueue<Integer> priorityQueue = new PriorityQueue<Integer>(4, new Comparator<Integer>() {
@Override
public int compare(Integer o1, Integer o2) {
return o1 - o2;
}
});
ArrayList<Integer> list = new ArrayList<>(Arrays.asList(1, 2, 3, 4, 5, 6, 7, 8, 9));
list.forEach(integer -> {
if (priorityQueue.size() < 4) {
priorityQueue.add(integer);
} else {
Integer peek = priorityQueue.peek();
if (peek < integer) {
priorityQueue.poll();
priorityQueue.add(integer);
}
}
});
priorityQueue.forEach(System.out::println);
}
}
1、PriorityQueue详解
2、深入理解Java PriorityQueue