定义与实例
Queue 是先进先出的队列,而 PriorityQueue 是优先级队列,其在执行 poll()/remove() 时,会优先取出权重最小的值,它的权重指的是元素的大小,有两种实现方式
- 通过构造方法里传入 Comparator 的实现
- 通过元素本身实现的 Comparator 接口
如果两个实现都没有的话,则退化为先进先出队列,下面就先来个例子
Queue<Integer> queue = new PriorityQueue<>();queue.offer(3);queue.offer(5);queue.offer(10);queue.offer(7);queue.offer(4);int size = queue.size();for (int i = 0; i < size; i++) {System.out.println(queue.poll());}// 输出345710
深入理解
数据结构
PriorityQueue 内部的数据结构是一个实现了二叉堆算法的数组(小顶堆,任意一个非叶子节点的权值,都不大于其左右子节点的权值)
二叉堆: 二叉堆就是完全二叉树,是平衡树的一种,下面举个例子去看下 例如依次插入 3、5、10、2、30、6、12、1
提问:为什么要用完全二叉树呢,用红黑树不香吗? 答:这是根据使用场景而定的,完全二叉树有个很好的理由就是取最小值的性能是 O(1)
刚刚上边的引用里依次插入了 3、5、10、2、30、6、12、1,那么在 PriorityQueue 中数组存储为
| 1 | 2 | 6 | 3 | 30 | 10 | 12 | 5 |
|---|---|---|---|---|---|---|---|
小知识点: 二叉树用数组存储的话,父节点、左节点、右节点公式如下:
- 父节点:(nodeNo-1)/2
- 左节点:(parentNo*2)+1
- 右节点:(parentNo*2)+2
源码方法解析
这章主要是源码解析,涉及到三类
- 入队:add、offer
- 检查:element、peek
- 出队:remove、poll
- 指定删除:remove(Object o)
add 和 offer
在 PriorityQueue 中 add 和 offer 是一致的,add 内部就是调用了 offer,没有任何其他的行为。看 offer 的源码除了扩容外,最主要的就是 siftUpUsingComparator(int insertIndex,E value,Object[] array,Comparator<? super T> cmp) 方法,下面把这段代码复制出来,insertIndex 默认先插入到数组最后
private static <T> void siftUpUsingComparator(int insertIndex, T value, Object[] es, Comparator<? super T> cmp) {while (insertIndex > 0) {int parent = (insertIndex - 1) >>> 1; // parentNo = (nodeNo-1)/2 获取父节点在数组中的角标Object e = es[parent]; 获取父节点的值if (cmp.compare(value, (T) e) >= 0) 如果要插入的值大于父节点,则找到了要插入数组角标的值break;es[insertIndex] = e;否则当前 insertIndex 角标与父节点角标交换(也就是插入节点与父节点交换),然后循环接着与父节点比较insertIndex = parent;}es[insertIndex] = value;}
element 和 peek
element 和 peek 都是获取首元素但不删除,只是在没有首元素的时候处理的行为有所不同,element 会直接抛出异常而 peek 则返回 null,看源码 element 其实是调用了 peek 的。而 peek 的代码就很简单,就是返回 index 为 0 的数值,如下
public E peek() {return (E) queue[0];}
remove 和 poll
remove 和 poll 都是获取并删除首元素,在没有首元素时,remove 会抛出异常而 poll 则返回空,由于 remove 会调用 poll,则下面就主要分析 poll 的代码
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;}
提问:为什么要用完全二叉树呢,用红黑树不香吗?
答:这是根据使用场景而定的,完全二叉树有个很好的理由就是取最小值的性能是 O(1)
