简介
PriorityQueue(优先级队列)的数据结构是二叉堆。准确的说是一个小顶堆。
二叉堆是一个特殊的堆, 它近似完全二叉树。
当父节点的键值总是大于或等于任何一个子节点的键值时为大顶堆。 当父节点的键值总是小于或等于任何一个子节点的键值时为小顶堆。
PriorityQueue不是线程安全的, 类似的PriorityBlockingQueue是线程安全的。
源码分析
主要属性
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; // 修改次数
主要方法
offer(E e)
add 内部也是调用的此方法
public boolean offer(E e) {
if (e == null)
throw new NullPointerException();
modCount++; // 修改次数+1
int i = size; // 数组当前容量
if (i >= queue.length) // 如果容量大于队列长度
grow(i + 1); // 扩容
size = i + 1; // 容量+1
if (i == 0) // 首次添加
queue[0] = e;
else
siftUp(i, e); // 上浮
return true;
}
private void siftUp(int k, E x) {
if (comparator != null) // comparator 不为null
siftUpUsingComparator(k, x);
else // 如果为null,就用元素的自然排序
siftUpComparable(k, x);
}
@SuppressWarnings("unchecked")
private void siftUpComparable(int k, E x) {
// k 数组元素下标
// x 元素
// 如果元素没有实现Comparable接口,此处会出现类型转换异常
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; // 调整结束,放入k位置
}
(1)入队不允许null元素;
(2)如果数组不够用了,先扩容;
(3)如果还没有元素,就插入下标0的位置;
(4)如果有元素了,就插入到最后一个元素往后的一个位置(实际并没有插入哈);
(5)自下而上堆化,一直往上跟父节点比较;
(6)如果比父节点小,就与父节点交换位置,直到出现比父节点大为止;
poll()
public E poll() {
if (size == 0) // 数组没有元素
return null;
int s = --size; // 数组最后一个元素的下标
modCount++; // 修改次数+1
E result = (E) queue[0]; // 因为是小顶堆,每次poll都是返回数组的第一个元素
E x = (E) queue[s]; // 拿到数组的最后一个元素
queue[s] = null; // 最后位置赋值为null
if (s != 0) // 容量多于一个
siftDown(0, x); // 下沉操作,把x放到下标为0的位置,和子类比较,如果大于子类,执行下沉操作,直到找到合适的位置
return result;
}
private void siftDown(int k, E x) {
if (comparator != null)
siftDownUsingComparator(k, x);
else // 自然排序
siftDownComparable(k, x);
}
@SuppressWarnings("unchecked")
private void siftDownComparable(int k, E x) {
// k 数组元素下标
// x 元素
// 如果元素没有实现Comparable接口,此处会出现类型转换异常
Comparable<? super E> key = (Comparable<? super E>)x;
int half = size >>> 1; // 只比较数组容量的一半(叶子节点就占用了一半元素)
while (k < half) {
/**
* 2*n+1 左子节点
* 2*(n+1) 右子节点
*/
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; // 放入调整之后的位置
}
(1)将队列首元素弹出;
(2)将队列末元素移到队列首;
(3)自上而下堆化,一直往下与最小的子节点比较;
(4)如果比最小的子节点大,就交换位置,再继续与最小的子节点比较;
(5)如果比最小的子节点小,就不用交换位置了,堆化结束;
(6)这就是堆中的删除堆顶元素;
grow(int minCapacity)
private void grow(int minCapacity) {
int oldCapacity = queue.length; // 当前数组容量
// Double size if small; else grow by 50%
/*
如果当前数组容量小于64,扩容容量就翻倍,反之就扩容之前的一半
*/
int newCapacity = oldCapacity + ((oldCapacity < 64) ?
(oldCapacity + 2) :
(oldCapacity >> 1));
// overflow-conscious code
if (newCapacity - MAX_ARRAY_SIZE > 0) // 扩容之后的容量如果大于允许的最大容量
newCapacity = hugeCapacity(minCapacity); // 是否溢出
queue = Arrays.copyOf(queue, newCapacity); // 拷贝新的数组
}
private static int hugeCapacity(int minCapacity) {
if (minCapacity < 0) // 容量溢出
throw new OutOfMemoryError();
return (minCapacity > MAX_ARRAY_SIZE) ?
Integer.MAX_VALUE :
MAX_ARRAY_SIZE;
}
(1)当数组比较小(小于64)的时候每次扩容容量翻倍;
(2)当数组比较大的时候每次扩容只增加一半的容量;
remove(Object o)
public boolean remove(Object o) {
int i = indexOf(o); // 找到元素o在数组中的下标
if (i == -1) // 数组中没有此元素
return false;
else {
removeAt(i); // 删除元素
return true;
}
}
private E removeAt(int i) {
// assert i >= 0 && i < size;
modCount++; // 修改次数+1
int s = --size; // 最后一个元素的下标
if (s == i) // 如果删除的是最后一个元素
queue[i] = null;
else { // 删除的不是最后一个
E moved = (E) queue[s]; // 拿到最后一个元素
queue[s] = null; // 最后一个元素赋值null
siftDown(i, moved); // 先执行下沉操作,如果能够向下调整,就无须上浮操作了
if (queue[i] == moved) { // 没有向下调整
siftUp(i, moved); // 上浮操作
if (queue[i] != moved)
return moved;
}
}
return null;
}