1 概述
PriorityQueue是基于优先堆实现的无界优先队列,它的底层是通过最小堆来实现的。我们知道普通的队列具有FIFO的特性,队列的出和入与进入队列的时间有关,但是针对优先队列,每个队列的元素中都有一个属性来表示队列的优先级,然后队列的出队就根据队列中元素的优先级来确定先后顺序。这样就可以实现生活中一些需要根据优先级来处理数据的场景,比如说根据会员级别,优先消费的场景。下面我们来看看PriorityQueue的具体内容。
2 基本信息
(1)数据结构
图片来源于网络
我们知道堆的数据结构是二叉树,而最小堆其实就是二叉树的顶部是最小值的结构。PriorityQueue的逻辑实现是基于二叉树来实现的,而数据存储是基于数组的。
(2)基础函数
add(E e) 添加元素(内部直接调用offer(E e))
offer(E e) 添加元素
peek() 读取元素,不删除
poll() 读取元素,并删除
contains(Obect e) 检测是否包含当前参数
remove(Object o) 移除指定元素
clear() 清空
3 源码分析
(1)属性
//数组默认容量
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;
(2)函数
针对函数的分析,这里我们仅仅分析部分核心函数,其余的函数大家可以自行查看源码,比较简单。
offer函数
入队操作可以简化成下图的流程。
图片来源于网络
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函数从下往上调整堆
siftUp(i, e);
return true;
}
这里我们重点看下siftUp函数。
private void siftUpUsingComparator(int k, E x) {
//循环遍历二叉树直到树顶
while (k > 0) {
//获取k的父节点索引
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;
}
这里的逻辑比较简单,就不进一步说明了。接下来我们来看看poll函数。
poll函数
poll每次删除元素都是删除二叉树的头节点,删除头节点后再将最后一个节点移到头节点处,然后进行调整。
删除元素后对堆进行调整。
图片来源于网络
堆中每次删除就只能删除头节点,也就是数组中第一个节点。
图片来源于网络
将最后一个节点替代头节点,然后进行调整。
图片来源于网络
如果左右节点中的最小节点比当前节点小就与左右节点的最小节点交换。直到当前节点无子节点,或者当前节点比左右节点小时停止交换。
下面我们来看一下poll函数的源码。
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;
}
当堆中不止一个元素的时候,在移除顶节点后需要对堆进行调整,调整函数siftDown的源码如下。
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];
//x小于或等于子节点
if (comparator.compare(x, (E) c) <= 0)
break;
//将子节点上提到父节点位置,k设置为子节点的值
queue[k] = c;
k = child;
}
//设置x在合适的位置
queue[k] = x;
}
上面就是对PriorityQueue的源码分析,总地来说比较简单,只要注意二叉树是如何调整就行了,欢迎交流,谢谢!
参考资料: