基于jdk1.8源码进行分析的。
上面刚分析了优先阻塞队列,是基于二叉堆实现的,下面我们看一下优先队列的实现。
类继承结构
public class PriorityQueue<E> extends AbstractQueue<E>
implements java.io.Serializable
继承了AbstractQueue列,实现了Serializable接口。
成员属性
//默认初始化大小
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; // non-private to simplify nested class access
构造方法
PriorityQueue()
public PriorityQueue() {
this(DEFAULT_INITIAL_CAPACITY, null);
}
默认构造方法,初始化大小为11,调用内部其他构造方法实现的。
PriorityQueue(int initialCapacity)
public PriorityQueue(int initialCapacity) {
this(initialCapacity, null);
}
可以指定容量大小,调用内部其他构造方法实现的。
PriorityQueue(Comparator<? super E> comparator)
public PriorityQueue(Comparator<? super E> comparator) {
this(DEFAULT_INITIAL_CAPACITY, comparator);
}
指定比较器的构造方法,默认初始化容量大小为11,调用其他内部构造方法实现。
PriorityQueue(int initialCapacity,Comparator<? super E> comparator)
public PriorityQueue(int initialCapacity,
Comparator<? super E> comparator) {
// Note: This restriction of at least one is not actually needed,
// but continues for 1.5 compatibility
//数据合法性校验
if (initialCapacity < 1)
//如果不合法,抛出异常
throw new IllegalArgumentException();
//初始化数组
this.queue = new Object[initialCapacity];
//初始化比较器
this.comparator = comparator;
}
以上三个都是调用该构造方法实现的。
PriorityQueue(Collection<? extends E> c)
@SuppressWarnings("unchecked")
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);
}
}
通过结合进行初始化,默认调用上面的构造方法实现的。
核心方法
add(E e)
添加元素
public boolean add(E e) {
return offer(e);
}
通过源码我们可以看到是通过调用内部的offer方法实现的。
offer(E e)
队列添加元素具体实现
public boolean offer(E e) {
//添加元素为空,直接抛出异常
if (e == null)
throw new NullPointerException();
//动态维护修改次数
modCount++;
//记录当前优先队列中元素葛素
int i = size;
//如果满了,就需要扩容
if (i >= queue.length)
//扩容调用方法,后面分析
//参数size+1
grow(i + 1);
//动态维护size
size = i + 1;//i=size
//如果size
if (i == 0)
//队列为空,添加第一个元素,也就是数组第一个元素
queue[0] = e;
else
//动态添加元素,并维护堆特性
siftUp(i, e);
//添加成功
return true;
}
grow(int minCapacity)
用于实现堆扩容操作。
//参数size+1
private void grow(int minCapacity) {
//当前队列长度
int oldCapacity = queue.length;
// Double size if small; else grow by 50%
// 如果比较小,可以看到是和64相比较,扩展为两倍
// 否则,增长为1.5倍
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);
}
里面涉及到方法 hugeCapacity,源码如下:
hugeCapacity(int minCapacity)
private static int hugeCapacity(int minCapacity) {
//size+1<0
//数据合法性校验
if (minCapacity < 0) // overflow
//抛出异常
throw new OutOfMemoryError();
//返回较大值
return (minCapacity > MAX_ARRAY_SIZE) ?
Integer.MAX_VALUE :
MAX_ARRAY_SIZE;
}
下面我们看看,如果不需要扩容,或者扩容完毕,且优先队列中有元素的时候的操作siftUp是这么实现的。
siftUp(int k, E x)
//参数:i,e
//添加元素具体实现
private void siftUp(int k, E x) {
//比较器为空
if (comparator != null)
//没有比较器具体实现方法
siftUpUsingComparator(k, x);
else
//指定比较器具体实现方法
siftUpComparable(k, x);
}
我们继续往下看调用到的方法。
siftUpUsingComparator(int k, E x)
指定比较器的时候,调用的方法。
//参数:i=size,e
//这个过程叫做在算法我们都知道成为上滤操作
@SuppressWarnings("unchecked")
private void siftUpUsingComparator(int k, E x) {
//size>0
//队列有元素
while (k > 0) {
//获取父节点索引
int parent = (k - 1) >>> 1;
//获取父节点元素
Object e = queue[parent];
//比较器比较元素大小
//这个比较器是构造方法传入的,用户自定义,如果符合条件,直接跳出循环
if (comparator.compare(x, (E) e) >= 0)
break;
//调整,满足二叉堆性质
//父子交换,小的元素往前放,也就是树的顶端位置方向
queue[k] = e;
k = parent;
}
//调整完毕,将对应k位置的索引在数组中的位置指定位元素x,也就是要添加的元素
queue[k] = x;
}
siftUpComparable(int k, E x)
没有指定比较器的时候调用的方法
//参数:i=size,e
@SuppressWarnings("unchecked")
private void siftUpComparable(int k, E x) {
//题外话:补充个知识点
//<? extends E> 只能是E或者E的子类,上限
//<? super E> 只能是E或者E的附列,下限
//这里操作是一个强转类型的操作
Comparable<? super E> key = (Comparable<? super E>) x;
//size>0
//优先队列有元素
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()
方法用于取出元素。
public E poll() {
//优先队列没有元素
if (size == 0)
//返回null
return null;
//队列有元素,动态维护size
int s = --size;
//维护修改次数
modCount++;
//队列首元素,或者说是数组首元素
E result = (E) queue[0];
//s=--size
//数组最后一个元素
E x = (E) queue[s];
//队列最后索引位置置空
queue[s] = null;
//s=--size
//队列中不只一个元素的时候,如果有一个,移除后数组为空,那么不需要执行,直接返回结果
if (s != 0)
//进行下滤操作
siftDown(0, x);
//返回队列首元素
return result;
}
下面我们看一下对应源码中调用到的其他方法的源码。
siftDown(int k, E x)
执行删除元素具体实现。
//参数:0,x
private void siftDown(int k, E x) {
//如果指定了构造器
if (comparator != null)
//调用该方法
siftDownUsingComparator(k, x);
else
//如果没有指定构造器,那么调用该方法
siftDownComparable(k, x);
}
//参数:0,x
@SuppressWarnings("unchecked")
private void siftDownUsingComparator(int k, E x) {
//half=size/2
int half = size >>> 1;
//遍历整体数组的一半元素
//如果k小于half,则k位置的元素就不是叶子节点
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为右孩子对应节点的元素,寻找右孩子最小的元素
c = queue[child = right];
//如果要添加的元素小于左孩子
if (comparator.compare(x, (E) c) <= 0)
//循环结束
break;
//队尾元素比根元素孩子都大,则需要"下移"
//交换根元素和孩子c的位置
queue[k] = c;
//将根元素位置k指向最小孩子的位置,进入下层循环
k = child;
}
//找到队尾元素x的合适位置k之后进行赋值
queue[k] = x;
}
siftDownComparable(int k, E x)
过程和上面类似,区别在于没有指定比较器的时候调用该方法。
//参数: 0,x
@SuppressWarnings("unchecked")
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;
}
此处不再介绍和粘贴peek和indexOf等方法源码,下面直接看一下remove方法的源码实现。
remove(Object o)
public boolean remove(Object o) {
int i = indexOf(o);
if (i == -1)
return false;
else {
removeAt(i);
return true;
}
}
溢出元素,首先找到o对应的下表的位置。如果没有找到,直接返回-1,否则调用removeAt方法实现。
removeAt(int i)
private E removeAt(int i) {
// assert i >= 0 && i < size;
//维护修改次数
modCount++;
//size-1
int s = --size;
//如果移除的是最后一个元素
if (s == i) // removed last element
//最后一个元素置空
queue[i] = null;
else {
//记录最后一个元素
E moved = (E) queue[s];
//最后一个元素置空
queue[s] = null;
//进行下滤操作
siftDown(i, moved);
if (queue[i] == moved) {
siftUp(i, moved);
if (queue[i] != moved)
return moved;
}
}
return null;
}
以上是整个优先队列的源码分析过程,如果有不对的地方还请指正。