PriorityQueue 源碼解析

前言

PriorityQueue是優(yōu)先隊列, 也就是說在隊列中的元素是有優(yōu)先級的, 優(yōu)先級高的元素會先出隊列.

本文源碼: 本文源碼下載

例子

先以一個簡單的例子來了解一下PriorityQueue的簡單用法.

package com.priorityqueue;

import java.util.Comparator;

public class Test01 {
    public static void main(String[] args) {
        PriorityQueue<Integer> pq = new PriorityQueue<>(10, new Comparator<Integer>() {
            @Override
            public int compare(Integer o1, Integer o2) {
                return o1 - o2;
            }
        });
        pq.add(1);
        pq.add(3);
        pq.add(2);
        pq.add(0);
        while (!pq.isEmpty()) {
            System.out.print(pq.poll() + " ");
        }
    }
}

輸出如下: 可以看到輸出結(jié)果并不是按加入的順序輸出,而是按照優(yōu)先級大小輸出的.

0 1 2 3

源碼

屬性

// 默認(rèn)隊列容量大小
    private static final int DEFAULT_INITIAL_CAPACITY = 11;

    /**
     * 存儲元素用的數(shù)組
     */
    transient Object[] queue; // non-private to simplify nested class access

    /**
     * 當(dāng)前隊列中元素的個數(shù)
     */
    private int size = 0;

    /**
     * The comparator, or null if priority queue uses elements'
     * natural ordering.
     *
     * 比較器
     */
    private final Comparator<? super E> comparator;

    /**
     * The number of times this priority queue has been
     * <i>structurally modified</i>.  See AbstractList for gory details.
     */
    transient int modCount = 0; // non-private to simplify nested class access

構(gòu)造函數(shù)

    public PriorityQueue(int initialCapacity) {
        this(initialCapacity, null);
    }
    public PriorityQueue(Comparator<? super E> comparator) {
        this(DEFAULT_INITIAL_CAPACITY, 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;
    }

加入元素

/**
     * 將元素e加入到優(yōu)先隊列中
     * 以下情況下會報異常:
     * 1. e為null會拋出NullPointerException異常
     * 2. e無法比較會拋出ClassCastException異常
     * 3. 容量達(dá)到極限拋出OutofMemoryException異常
     *
     * @return {@code true} (as specified by {@link Collection#add})
     * @throws ClassCastException if the specified element cannot be
     *         compared with elements currently in this priority queue
     *         according to the priority queue's ordering
     * @throws NullPointerException if the specified element is null
     */
    public boolean add(E e) {
        return offer(e);
    }

    /**
     * 將元素e加入到優(yōu)先隊列中
     * 以下情況下會報異常:
     * 1. e為null會拋出NullPointerException異常
     * 2. e無法比較會拋出ClassCastException異常
     * 3. 容量達(dá)到極限拋出OutofMemoryException異常
     * @return {@code true} (as specified by {@link Queue#offer})
     * @throws ClassCastException if the specified element cannot be
     *         compared with elements currently in this priority queue
     *         according to the priority queue's ordering
     * @throws NullPointerException if the specified element is null
     */
    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(i, e);
        return true;
    }

可以看到最終調(diào)用的是siftUp(i, e)方法, 接下來看看該方法是如何實現(xiàn)的.

    private void siftUp(int k, E x) {
        if (comparator != null)
            siftUpUsingComparator(k, x);
        else
            siftUpComparable(k, x);
    }

    @SuppressWarnings("unchecked")
    private void siftUpComparable(int k, E x) {
        Comparable<? super E> key = (Comparable<? super E>) x;
        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;
    }

    @SuppressWarnings("unchecked")
    private void siftUpUsingComparator(int k, E x) {
        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;
        }
        queue[k] = x;
    }

看著代碼可能不那么好理解, 接下來以一個例子來輔助理解這段代碼.

圖片.png

上圖是一個優(yōu)先隊列,藍(lán)色字代表該節(jié)點對應(yīng)的數(shù)組下標(biāo), 對應(yīng)的數(shù)組如下:


圖片.png

可以看到堆是一個完全二叉樹, 如果一個節(jié)點的下標(biāo)是i, 則該節(jié)點的父親節(jié)點下標(biāo)是(i - 1) / 2, 根節(jié)點下標(biāo)是0.

現(xiàn)在看看往堆中增加一個元素2是如何操作的.

圖片.png

最終用數(shù)組的表現(xiàn)形式如下:


圖片.png

消費元素

@SuppressWarnings("unchecked")
    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, 思路是取出下標(biāo)為0的節(jié)點并且保存最后一個節(jié)點的元素, 相當(dāng)于把最后一個節(jié)點放到下標(biāo)為0的位置, 因為有可能此操作會導(dǎo)致堆的性質(zhì)被破壞, 所以需要利用siftDown來維持堆的性質(zhì).

private void siftDown(int k, E x) {
        if (comparator != null)
            siftDownUsingComparator(k, x);
        else
            siftDownComparable(k, 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;
    }

    @SuppressWarnings("unchecked")
    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];
            if (comparator.compare(x, (E) c) <= 0)
                break;
            queue[k] = c;
            k = child;
        }
        queue[k] = x;
    }

相對于siftUp需要去找到parent, siftDown則需要去找到兩個child, 如果當(dāng)前節(jié)點的下標(biāo)是i, 那此節(jié)點的左孩子節(jié)點下標(biāo)為2 * i + 1, 右孩子下標(biāo)為2 * i + 2.

以下也是通過一個小例子來解析該代碼要做的事情.


圖片.png

對應(yīng)的數(shù)組表示如下:


圖片.png

刪除元素

   /** 返回值:(遍歷的時候會用到)
     * 如果往下調(diào)整 則返回null
     * 如果往上調(diào)整 則需要返回被調(diào)整的moved值
     *
     */
@SuppressWarnings("unchecked")
    private E removeAt(int i) {
        // assert i >= 0 && i < size;
        modCount++;
        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;
    }

可以看到該方法是刪除在下標(biāo)i的元素, 如果是最后一個元素可以直接刪除而不會影響堆的性質(zhì), 如果不是則需要先模擬刪除最后一個元素然后把該元素插入到i的位置, 因為此時不知道放到i這個位置是對堆的結(jié)構(gòu)上面有影響還是下側(cè)有影響, 因此就先通過往下調(diào)整來嘗試, 如果往下調(diào)整成功則不需要往上調(diào)整, 否則需要繼續(xù)往上調(diào)整.

遍歷元素

遍歷元素Iterator的本質(zhì)作用就是遍歷整個元素,在遍歷的過程中可通過iterator.remove()來刪除元素.

先通過一個例子來看看如何工作的.

package com.priorityqueue;

import java.util.Comparator;
import java.util.Iterator;

public class Test02 {
    public static void main(String[] args) {
        PriorityQueue<Integer> pq = new PriorityQueue<>(10, new Comparator<Integer>() {
            @Override
            public int compare(Integer o1, Integer o2) {
                return o1 - o2;
            }
        });
        pq.add(1);
        pq.add(3);
        pq.add(12);
        pq.add(4);
        pq.add(6);
        pq.add(17);
        pq.add(13);
        pq.add(8);
        pq.add(5);
        pq.add(10);
        pq.add(11);
        pq.add(19);
        pq.add(23);
        pq.add(14);

        System.out.println(pq);

        Iterator<Integer> iter = pq.iterator();
        while (iter.hasNext()) {
            int val = iter.next();
            //if (val == 23) iter.remove();
            System.out.print(val + " ");
        }
    }
}

輸出如下: 正常輸出.

[1, 3, 12, 4, 6, 17, 13, 8, 5, 10, 11, 19, 23, 14]
1 3 12 4 6 17 13 8 5 10 11 19 23 14 

那如果在遍歷過程中如果刪除元素會怎么樣呢掌敬?把上文代碼中打開刪除元素23. 可以得知在刪除過程中需要調(diào)整整個堆的結(jié)構(gòu),也就疑問著原有結(jié)構(gòu)會產(chǎn)生變化. 至于會產(chǎn)生什么變化, 看下圖.

圖片.png

可以看到當(dāng)Iterator遍歷到23時, 然后刪除23節(jié)點時, 會調(diào)整到右側(cè)的圖結(jié)構(gòu), 可以看到調(diào)整后14節(jié)點所在的位置也就被遍歷過了, 14這個節(jié)點是如何被遍歷的呢?

答案是在刪除的過程中如果是需要向上調(diào)整時最后的一個元素(比如14)可能會被調(diào)整到一個被已經(jīng)遍歷過的位置(下標(biāo)5), 但是會有一個ArrayDeque對象來加入到這個節(jié)點,等到正常順序遍歷完后會從ArrayDeque對象取元素. 當(dāng)向下調(diào)整時就沒有必要加入到ArrayDeque對象中,因為下面的元素都還沒有被遍歷過.

    public Iterator<E> iterator() {
        return new Itr();
    }

    private final class Itr implements Iterator<E> {
        /**
         * 當(dāng)前遍歷到元素下標(biāo)
         */
        private int cursor = 0;

        /**
         * 上個被遍歷的元素下標(biāo)
         */
        private int lastRet = -1;

        /**
         * 存放因為刪除元素而導(dǎo)致被替換到已經(jīng)被遍歷過的下標(biāo)所在的位置上
         * 放到正常遍歷結(jié)束后再取這里所有的元素
         */
        private ArrayDeque<E> forgetMeNot = null;

        /**
         * 在遍歷forgetMeNot時上個遍歷元素
         */
        private E lastRetElt = null;

        /**
         * The modCount value that the iterator believes that the backing
         * Queue should have.  If this expectation is violated, the iterator
         * has detected concurrent modification.
         */
        private int expectedModCount = modCount;

        public boolean hasNext() {
            return cursor < size ||
                    (forgetMeNot != null && !forgetMeNot.isEmpty());
        }

        @SuppressWarnings("unchecked")
        public E next() {
            if (expectedModCount != modCount)
                throw new ConcurrentModificationException();
            if (cursor < size) //正常遍歷情況
                return (E) queue[lastRet = cursor++];
            if (forgetMeNot != null) { // 從forgetMeNot中取元素
                //System.out.println("in forgetMeNot != null next()");
                lastRet = -1;
                lastRetElt = forgetMeNot.poll();
                if (lastRetElt != null)
                    return lastRetElt;
            }
            throw new NoSuchElementException();
        }

        public void remove() {
            if (expectedModCount != modCount)
                throw new ConcurrentModificationException();
            if (lastRet != -1) { // 正常遍歷過程中刪除元素
                /**
                 * moved為null 表明是不會影響遍歷情況
                 * 否則需要加入到ArrayDeque留作后續(xù)遍歷 因為該元素被調(diào)整到之前已經(jīng)被遍歷的下標(biāo)位置了
                 */
                E moved = PriorityQueue.this.removeAt(lastRet);
                lastRet = -1;
                if (moved == null)
                    cursor--;
                else {
                    if (forgetMeNot == null)
                        forgetMeNot = new ArrayDeque<>();
                    //System.out.println("forgetMeNot add moved:" + moved);
                    forgetMeNot.add(moved);
                }
            } else if (lastRetElt != null) { // 遍歷ArrayDeque過程中刪除元素
                PriorityQueue.this.removeEq(lastRetElt);
                lastRetElt = null;
            } else {
                throw new IllegalStateException();
            }
            expectedModCount = modCount;
        }
    }

擴容

因為PriorityQueue是被設(shè)計成無界的,所以當(dāng)元素越來越多時需要進(jìn)行擴大容量. 代碼比較簡單就不多說了.

參考

1. Java1.8 源碼

最后編輯于
?著作權(quán)歸作者所有,轉(zhuǎn)載或內(nèi)容合作請聯(lián)系作者
  • 序言:七十年代末养葵,一起剝皮案震驚了整個濱河市,隨后出現(xiàn)的幾起案子,更是在濱河造成了極大的恐慌疤估,老刑警劉巖,帶你破解...
    沈念sama閱讀 218,525評論 6 507
  • 序言:濱河連續(xù)發(fā)生了三起死亡事件霎冯,死亡現(xiàn)場離奇詭異铃拇,居然都是意外死亡,警方通過查閱死者的電腦和手機沈撞,發(fā)現(xiàn)死者居然都...
    沈念sama閱讀 93,203評論 3 395
  • 文/潘曉璐 我一進(jìn)店門慷荔,熙熙樓的掌柜王于貴愁眉苦臉地迎上來,“玉大人缠俺,你說我怎么就攤上這事显晶。” “怎么了壹士?”我有些...
    開封第一講書人閱讀 164,862評論 0 354
  • 文/不壞的土叔 我叫張陵磷雇,是天一觀的道長。 經(jīng)常有香客問我躏救,道長唯笙,這世上最難降的妖魔是什么螟蒸? 我笑而不...
    開封第一講書人閱讀 58,728評論 1 294
  • 正文 為了忘掉前任,我火速辦了婚禮崩掘,結(jié)果婚禮上七嫌,老公的妹妹穿的比我還像新娘。我一直安慰自己苞慢,他們只是感情好诵原,可當(dāng)我...
    茶點故事閱讀 67,743評論 6 392
  • 文/花漫 我一把揭開白布。 她就那樣靜靜地躺著挽放,像睡著了一般皮假。 火紅的嫁衣襯著肌膚如雪。 梳的紋絲不亂的頭發(fā)上骂维,一...
    開封第一講書人閱讀 51,590評論 1 305
  • 那天惹资,我揣著相機與錄音,去河邊找鬼航闺。 笑死褪测,一個胖子當(dāng)著我的面吹牛,可吹牛的內(nèi)容都是我干的潦刃。 我是一名探鬼主播侮措,決...
    沈念sama閱讀 40,330評論 3 418
  • 文/蒼蘭香墨 我猛地睜開眼,長吁一口氣:“原來是場噩夢啊……” “哼乖杠!你這毒婦竟也來了分扎?” 一聲冷哼從身側(cè)響起,我...
    開封第一講書人閱讀 39,244評論 0 276
  • 序言:老撾萬榮一對情侶失蹤胧洒,失蹤者是張志新(化名)和其女友劉穎畏吓,沒想到半個月后,有當(dāng)?shù)厝嗽跇淞掷锇l(fā)現(xiàn)了一具尸體卫漫,經(jīng)...
    沈念sama閱讀 45,693評論 1 314
  • 正文 獨居荒郊野嶺守林人離奇死亡菲饼,尸身上長有42處帶血的膿包…… 初始之章·張勛 以下內(nèi)容為張勛視角 年9月15日...
    茶點故事閱讀 37,885評論 3 336
  • 正文 我和宋清朗相戀三年,在試婚紗的時候發(fā)現(xiàn)自己被綠了列赎。 大學(xué)時的朋友給我發(fā)了我未婚夫和他白月光在一起吃飯的照片宏悦。...
    茶點故事閱讀 40,001評論 1 348
  • 序言:一個原本活蹦亂跳的男人離奇死亡,死狀恐怖包吝,靈堂內(nèi)的尸體忽然破棺而出饼煞,到底是詐尸還是另有隱情,我是刑警寧澤诗越,帶...
    沈念sama閱讀 35,723評論 5 346
  • 正文 年R本政府宣布砖瞧,位于F島的核電站,受9級特大地震影響掺喻,放射性物質(zhì)發(fā)生泄漏芭届。R本人自食惡果不足惜储矩,卻給世界環(huán)境...
    茶點故事閱讀 41,343評論 3 330
  • 文/蒙蒙 一、第九天 我趴在偏房一處隱蔽的房頂上張望褂乍。 院中可真熱鬧持隧,春花似錦、人聲如沸逃片。這莊子的主人今日做“春日...
    開封第一講書人閱讀 31,919評論 0 22
  • 文/蒼蘭香墨 我抬頭看了看天上的太陽褥实。三九已至呀狼,卻和暖如春,著一層夾襖步出監(jiān)牢的瞬間损离,已是汗流浹背哥艇。 一陣腳步聲響...
    開封第一講書人閱讀 33,042評論 1 270
  • 我被黑心中介騙來泰國打工岳遥, 沒想到剛下飛機就差點兒被人妖公主榨干…… 1. 我叫王不留风纠,地道東北人溺拱。 一個月前我還...
    沈念sama閱讀 48,191評論 3 370
  • 正文 我出身青樓旁蔼,卻偏偏與公主長得像,于是被迫代替她去往敵國和親邑滨。 傳聞我的和親對象是個殘疾皇子砸西,可洞房花燭夜當(dāng)晚...
    茶點故事閱讀 44,955評論 2 355