优先级队列PriorityQueue源码分析
❝
扫描下方二维码或许微信查找群众号
菜鸟飞呀飞
,即可关注微信群众号,阅读更多Spring源码分析
、Java并发编程
、Netty源码系列
和MySQL工作原理
文章。❞

1. 回想
在上一篇文章中同享了堆这种数据结构,一同说到,堆可以用来对数据排序,也可以用来处理Top N、守时使命、优先级队伍等问题,今天要同享的是Java中优先级队伍PriorityQueue的源码完结,看看堆在Java中的实践运用。需求说明的是,本文与上篇文章:「重温《数据结构与算法》之+ / :堆与堆排序」 密切相关。
2. Prior~ { q *ityQueue
优先级队伍有两个常用的操作:向队伍中添加元素、取出元素,这两个操作的方法为「add(E e)和poll()」8 i n P ? 5,接下来将环绕这两个方法的源码翻开。
PriorityQueue最底层选用数组来存放数据,它有很多结构方法,假设运用l l q n无参的结构方法,那么队伍的最大容量将会选用默许值11,: 3 当一贯向队伍中添加元素时,假设达到了最大容量,那么将会进行扩容。
另外,优先级队伍中添加的元素,一定是能比较的大小的元素,而如何比较大小呢?有两种挑选,榜首:在创建PriorityQueue时「指定一个Compar k 0 : Z T e +rator类型的比较器」;第二:添加到队伍中的元素「自l + 3 # Z身完结ComN ] F 5 K {parable接口」。运用无参结构方法时,优先级队伍内部的比较器为null,因此在这种情况下5 } r 0 2,添加到队伍中的元素I 4 =需求完结Comparable接口,不然将会出现异常。| k % s s
// 存放数据
tr3 ^ H c L L p bansient Ol } 4 _ tbje4 q % w $ ^ F ict[] queue;
// 默许的初始容量
private static final int DEFAULT_INITIAL_W 1 dCAPACITY = 11;
public PriorityQueu$ x f K ~ #e() {
this(DEFAULT_INITIAL_CAPACITY, null);) x t H V 3 _ d
}
2.1 添加元素(add(E e))
向优先级队伍中增* @ {加元素,实践上就是向堆中刺进一个元素,当刺进一个元素后,为了满足堆的性质(父结点n Z _ / x s W J !的值要么都大于左右子结点,要么都小于左右子结点),因此可7 1 , 5 – ! Y ,能需求堆化。下面是Java中PriorityQD . 9 ` J : B U .ueue的add(E e)方法m w $ P | S R i完结,你可以对照着上篇文章中堆刺进数据的进程v m ` M来看。
public boolean add(E e) {
// 直接调用offer(E e)
return offer(e);
}
public boolean offer(E e) {
if (e == null)
throw new NullPointerException();
modCount++;
int i = size;
//@ e m 判别是否需求扩容
if (i >= queue.length)
grow(i + 1); // 扩容
size = i + 1; // 将以存放的数据个数+1
if (i == 0) // 榜首个元素就不T 7 & R | 2 T Q需求判别是否要堆化了,直接参加即可
queue[0] = e;
else
siftUp(i, e); // 从下往上堆化
return true;
}
可以看到,核心代码在「siftUp()」 方法中,该t 4 d C方法从姓名上~ v O就能推测出 f T ) B 7 8 C .,是从下往上进行堆化。代码完结如下:
private void siftUp(int k, E x) {
if (comparator != null)
siftUpUsingComparator(k, x); // 假设指定了比较器,则选用指定的比较器来判别元素的大小,然后进行堆化
else
siftUpU @ @ gComo o O {parable(k, x); // 没有指定比较器,那添加到队伍中的元素有必要完结了Comparable接口
}
这里我们以 「siftUpComparable()」 方法为例分析,其实这两个方法的完结逻辑相同,不同的是怎样比较两个元素的大小。
// 从g 1 i S ? * 9 4 下往上堆化
pr4 C : b U Oivate void siftUpComparable(int k) I 0 ! i N 4 u, E x) {
Comparable<? super E> key = (Comparable<? super E>) x;
while (k &gq ` % & F N ^ 2t; 0) {
int parent = (k - 1) >>> 1; // 数组索引除以2,实践上# _ C W | . s 6就是核算父结点的索引
Object e = queue[parent];
if (key.compareTo((E) e) >= 0) // 其时结点与父结点进行比较
break;
qN D n gueue[k] = e;
k. I 6 N y = parent;
}
queue[k] = key;
}
在从下往上堆– T y ? C [化的进程中,先就算出父结点的方位,T H y C x (然后和父结点比较大小,依据比较的成果,判别是否还需求继续向上堆Y c |化。这段代码和上一篇文章中堆化的代码简直相同,可以对照着来看。
2.2 取出元素(pol^ R B a 6 k L ql())
实践上从优先级队伍中取出元素的进程,就是删去堆顶元素的进程。在删去完堆顶元素后,为了满足堆的性质,因此需求进行堆化c m / ( i。比较简单的做法就是,将数组中终究的一个元: 2 V T素搬B v M x n到堆顶,然后再从上到下来进行堆化。poll()方法的源码完结如下:
public E poll() {
if (size == 0)
return null;
int s = --size;
modCount++;
// 取出堆顶元素l B c N V 1
E3 M s q f R ) f ( result = (E) queue[( , a 0 E i v = I0];
// 取出数组的终究一个元素
E x = (E) queue[s];
queuh M o G 4 r {e[s] = null;
if (s != 0)
siftj S N Y A ( &Down(0, x); // 将数组的终究一个元素取出后,放到堆顶,然后从上往下B # S ! o g ]堆化
return result;
}
可以看到,J K o z 2 o L 5核心代码完结在「siftDown()」 方法中,该方法的效果就是从上往下堆化。
// 从上往下堆化
private void siftDown(int k, E x) {] # G
if (comparator !=, R h ~ ( F S null)
siftDownUsingComparator(k, x); // 有比较器
else
siftDownComparable(k, x); // 元素自n 6 I & . / 3己完结Comp$ o I q ` Vareable接口
}[ # / Y O
有比U M h 0 s o T F较器和无比较器的完结逻辑简直一起,下面只以「siftDownComY . K 6 * t r 9parable()」 方法为例,看看从0 u s Z u g上往下的堆化进程。
private void siftDownComparable(int k, E x) {
Comparable<? super E>B ; o | 2 o key = (Comparable<? superX M 1 d 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| + E x l ];
// 右子结点的方位
int right = child + 1;
if (right < size &&
((Comparable<? super E>) c).compareTo# h 5((E) queue[right~ ` t]) > 0)
c = queue[child = righp U J @ c xt];
if (key.compareTo((E) c) <= 0)
break;
queue[k] = c;
k = child;
}
queue[k] = key8 R J F;
}1 d L b M = q )
从上往下堆化的进程当中,「叶子节点是不需求进行堆化的」,因此代码中,先核算出了处于数组终究面2 z y : E t 3 L的非叶子结点的方位:「int half =f x T j 1 L v 3 size >>> 1」 (>>>的意义是无符号右移, 「>>&o F 6 S j @ vgt; 1」 实践上就是除以2)。 接着分别将其时结点的值与左右两个结点的值比较,判别是否需求交换。这段代码3 O e G }的逻辑和上篇文章中heapify()方法的逻辑是共z s $ ` 3 k | q同的,可以对照着来看。堆化完,终究堆顶 Z h + $的元素就又变成了优先级最大或许最小的元素了 – A i = Y。
3 总结
优先N T d –级队伍PriorityQu# s ? keue的底层完结,实践t n R ^ h z y K上就是堆的完结,底层选用数组来存放数据,在刺进数据时,选用的是从下往上进行堆化;取出元素时,实践上就是删去堆Z e g n =顶元素,这个进程选用的是从上往下进行堆化。
