PriorityQueue
即优先队列,是一个无界限队列。基于优先级堆,使用平衡二叉堆实现,底层体现是数组。
transient Object[] queue; // non-private to simplify nested class access
引用java8
源码中PriorityQueue
的类注释来说:
其队列中所有元素都按照它们的自然排序方法**(实现Comparable
接口),或通过在创建对象时传入比较器(Comparator
)**完成排序。
但在两种方式同时存在的情况下:优先使用Comparator比较器
。
//源码
private void siftDown(int k, E x) {
if (comparator != null) //如果比较器不存在,则使用自然排序
siftDownUsingComparator(k, x);
else
siftDownComparable(k, x);
}
private void siftUp(int k, E x) {
if (comparator != null) //如果比较器不存在,则使用自然排序
siftUpUsingComparator(k, x);
else
siftUpComparable(k, x);
}
该队列中元素不允许为null
,否则将会抛出NullPointerException
。
//源码
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;
}
该队列中不允许不支持排序的对象进入,否则将会抛出ClassCastException
。
//源码
public boolean offer(E e) {
if (e == null)
throw new NullPointerException();
modCount++;
int i = size; //第一次插入时 size = 0 所以 i = 0
if (i >= queue.length)
grow(i + 1);
size = i + 1;
if (i == 0) //故第一次插入时不会执行`else`代码块进行重排序
queue[0] = e;
else
siftUp(i, e); //当插入第二个元素时开始调用
return true;
}
private void siftUp(int k, E x) { //假设无自然排序 且 无比较器
if (comparator != null) //false
siftUpUsingComparator(k, x);
else
siftUpComparable(k, x); //使用自然排序
}
@SuppressWarnings("unchecked")
private void siftUpComparable(int k, E x) {
Comparable<? super E> key = (Comparable<? super E>) x; //由于没有自然排序,则转换时会抛出 `ClassCastException`
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;
}
该队列的头部存放队列中最小元素,若存在多个最小元素,则头部为其中任意一个最小元素。
poll()
、remove()
、peek()
、element()
都是通过访问头部元素而实现的。
//源码
public E peek() {
return (size == 0) ? null : (E) queue[0]; //说句题外话,有人知道评论告诉我!
//为什么这里要判断size == 0? 直接返回(E) queue[0],不也是同样的效果吗?
}
该队列是无界的,但同时又是有容量的,默认大小11
。在元素添加的过程中,队列会扩容,在任意时间其最小大小和size
一样大。
size
:队列内存储元素个数。
扩容源码:
//源码
private void grow(int minCapacity) {
int oldCapacity = queue.length;
// Double size if small; else grow by 50%
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);
}
当前容量< 64
时,扩容原有容量大小 + 2
个空间,则空间将变为原有容量 + 原有容量 + 2
,即2 * 原有容量
。
当前容量>= 64
时,扩容原有容量二进制表示 向右偏移 一个进制
即一半的空间,则扩容后容量变成150%
。
该队列虽然通过上文提及到的两种方式完成排序,且也使用私有内部类实现了Iterator<E>
接口,但队列并没有完成迭代器级别的排序。
因为,队列仅在插入和取出元素时才进行排序计算,且仅仅在排序规则上保证头部为最小元素。
所以想要依次按照大小取出元素不能使用迭代器遍历,而是要通过依次取出头部元素的方式来完成。
//y
/**
* Returns an iterator over the elements in this queue. The iterator
* does not return the elements in any particular order.
*
* @return an iterator over the elements in this queue
*/
public Iterator<E> iterator() {
return new Itr();
}
private final class Itr implements Iterator<E> {
//...
}
该队列是非同步的,多线程环境中不应该访问和修改同一个队列实例。
相反,使用线程安全的PriorityBlockingQueue
可以解决这个问题。
该线程提供时间复杂度为O(log(n))
的入列和出列方法。
提供时间复杂度为O(n)
的remove(obj)
和contains(obj)
方法。
以及恒定时间O(1)
的查看方法,peek()
、element()
、size()
。