优先级队列PriorityQueue源码分析

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

优先级队伍PriorityQueue源码分析
微信群众号

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 =顶元素,这个进程选用的是从上往下进行堆化。

优先级队伍PriorityQueue源码分析
微信群众号| ] n _ p l 9