JDK源碼之PriorityQueue解析
一.優(yōu)先隊列的應用
優(yōu)先隊列在程序開發(fā)中屢見不鮮,比如操作系統(tǒng)在進行進程調(diào)度時一種可行的算法是使用優(yōu)先隊列,當一個新的進程被fork()出來后,首先將它放到隊列的最后,而操作系統(tǒng)內(nèi)部的Scheduler負責不斷地從這個優(yōu)先隊列中取出優(yōu)先級較高的進程執(zhí)行;爬蟲系統(tǒng)在執(zhí)行時往往也需要從一個優(yōu)先級隊列中循環(huán)取出高優(yōu)先級任務并進行抓取??梢韵胍?,如果類似這樣的任務不適用優(yōu)先級進行劃分的話,系統(tǒng)必會出現(xiàn)故障,例如操作系統(tǒng)中低優(yōu)先級進程持續(xù)占用資源而高優(yōu)先級進程始終在隊列中等待。此外,優(yōu)先隊列在貪婪算法中也有一些應用。
二.優(yōu)先隊列的實現(xiàn)原理
優(yōu)先隊列的實現(xiàn)方式是使用二叉堆的結(jié)構(gòu),需要滿足以下兩條性質(zhì)(Heap property),這里以小頂堆為例講解:
1.任何結(jié)點的值都小于或等于其子節(jié)點的值。
2.所有結(jié)點從上到下,從左到右填入,即一棵完全二叉樹。
基于這兩條規(guī)律,二叉堆在實現(xiàn)中往往會使用一個數(shù)組,下面我們研究一下JDK中二叉堆(優(yōu)先隊列)的實現(xiàn)。
三.優(yōu)先隊列在JDK中的實現(xiàn)方式
研究源碼最好的方式是debug,看每一步變量的變化,我們可以簡單寫一個Demo,debug進源碼一探究竟:

這里我們簡單地創(chuàng)建一個優(yōu)先隊列,向其中添加三個元素,我們可以在代碼第一行打一個斷點,如果您使用Eclipse編輯器的話,接下來可以按F5進入源碼中:

代碼運行到這里,PriorityQueue調(diào)用自己的一個重載構(gòu)造器,第一個參數(shù)是數(shù)組默認大小,第二個是元素比較的Comparator,我們這里的Demo比較簡單,您在使用優(yōu)先隊列時可以選擇實現(xiàn)自己的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;
}
接下來我們研究一下添加元素時的offer操作:
public boolean offer(E e) {
if (e == null)
throw new NullPointerException();
//記錄了隊列被修改的次數(shù)
modCount++;
int i = size;
if (i >= queue.length)
//擴容
grow(i + 1);
//增加元素個數(shù)
size = i + 1;
if (i == 0)
//第一次添加元素,直接放到第0個位置即可
queue[0] = e;
else
//需要將元素放到最后,再做上濾操作
siftUp(i, e);
return true;
}
我們逐行來解釋一下,首先offer方法判斷參數(shù)是否為空,不為空則對變量modCount自增,modCount記錄了隊列被修改的次數(shù),接下來,判斷數(shù)組是否會越界,如果越界則通過grow進行擴容,接下來添加元素,如果當前元素為0個則直接將元素放到數(shù)組第一個位置,否則做一個siftUp的操作。
private void grow(int minCapacity) {
int oldCapacity = queue.length;
// Double size if small; else grow by 50%
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);
上面的代碼對隊列擴容,源碼中注釋也很清晰,首先判斷當前的數(shù)組大小是否足夠小(<64),如果足夠小則將大小擴充為2倍,否則將原大小加上50%。需要注意的是,這里最后做了一個大小是否溢出的判斷。
private static int hugeCapacity(int minCapacity) {
if (minCapacity < 0) // overflow
throw new OutOfMemoryError();
return (minCapacity > MAX_ARRAY_SIZE) ?
Integer.MAX_VALUE :
MAX_ARRAY_SIZE;
}
如果需要擴容的大小已經(jīng)<0了,顯然已經(jīng)溢出了,在這里拋出了OutOfMemory的異常。
private void siftUpUsingComparator(int k, E x) {
while (k > 0) {
//計算父親節(jié)點的下標
int parent = (k - 1) >>> 1;
Object e = queue[parent];
//與父節(jié)點進行比較
if (comparator.compare(x, (E) e) >= 0)
break;
queue[k] = e;
k = parent;
}
queue[k] = x;
}
為了保證優(yōu)先隊列的性質(zhì)1,在插入每個元素時都需要與該節(jié)點父親進行比較,找到其正確位置,有些數(shù)據(jù)結(jié)構(gòu)書中,這個操作被稱為上濾(percolate up)。
入隊操作已經(jīng)說完了,接下來是出隊操作,即poll()操作:
public E poll() {
if (size == 0)
return null;
int s = --size;
//自增變量,代表隊列修改次數(shù)
modCount++;
E result = (E) queue[0];
E x = (E) queue[s];
queue[s] = null;
if (s != 0)
siftDown(0, x);
return result;
}
這個方法首先將數(shù)組第一個元素作為結(jié)果,(因為如果是小頂堆的話堆頂始終是最小元素),并將隊列的最后一個元素放到第一個位置,最后用siftDown做一些調(diào)整,保證隊列的性質(zhì),這個操作被稱為下濾(percolate down)。
private void siftDownUsingComparator(int k, E x) {
int half = size >>> 1;
//這里k必須有孩子,故葉節(jié)點需要比較
while (k < half) {
//以下幾行代碼到較小的那個兒子,用變量c表示
int child = (k << 1) + 1;
//這里假設(shè)左兒子比較小
Object c = queue[child];
int right = child + 1;
//左右兒子比較,如果右兒子小則將c賦值為右兒子
if (right < size &&
comparator.compare((E) c, (E) queue[right]) > 0)
c = queue[child = right];
//如果x比小兒子還小,說明k就是正確位置
if (comparator.compare(x, (E) c) <= 0)
break;
queue[k] = c;
k = child;
}
queue[k] = x;
}
如上圖,下濾過程中k不斷與其兒子進行比較,如果滿足優(yōu)先隊列的順序性,則break出循環(huán)。
以上就是本文的全部內(nèi)容,希望對大家的學習有所幫助,也希望大家多多支持腳本之家。
相關(guān)文章
spring 中事務注解@Transactional與trycatch的使用
這篇文章主要介紹了spring 中事務注解@Transactional與trycatch的使用,具有很好的參考價值,希望對大家有所幫助。如有錯誤或未考慮完全的地方,望不吝賜教2021-06-06
springboot中spring.profiles.include的妙用分享
這篇文章主要介紹了springboot中spring.profiles.include的妙用,具有很好的參考價值,希望對大家有所幫助。如有錯誤或未考慮完全的地方,望不吝賜教2022-08-08
SpringBoot配置MongoDB多數(shù)據(jù)源的方法步驟
這篇文章主要介紹了SpringBoot配置MongoDB多數(shù)據(jù)源的方法步驟,文中通過示例代碼介紹的非常詳細,對大家的學習或者工作具有一定的參考學習價值,需要的朋友們下面隨著小編來一起學習學習吧2020-10-10
如何在SpringBoot中使用Spring-AOP實現(xiàn)接口鑒權(quán)
這篇文章主要介紹了如何在SpringBoot中使用Spring-AOP實現(xiàn)接口鑒權(quán),文章圍繞主題展開詳細的內(nèi)容介紹,具有一定的參考價值,感興趣的小伙伴可以參考一下2022-09-09
springboot數(shù)據(jù)庫密碼加密的配置方法
這篇文章主要給大家介紹了關(guān)于springboot數(shù)據(jù)庫密碼加密的配置方法,文中通過示例代碼介紹的非常詳細,對大家的學習或者工作具有一定的參考學習價值,需要的朋友們下面隨著小編來一起學習學習吧2021-04-04
SpringBoot集成MybatisPlus報錯的解決方案
這篇文章主要介紹了SpringBoot集成MybatisPlus報錯的解決方案,文中通過示例代碼介紹的非常詳細,對大家的學習或者工作具有一定的參考學習價值,需要的朋友可以參考下2019-12-12
解決后端傳long類型數(shù)據(jù)到前端精度丟失問題
這篇文章主要介紹了解決后端傳long類型數(shù)據(jù)到前端精度丟失問題,具有很好的參考價值,希望對大家有所幫助,如有錯誤或未考慮完全的地方,望不吝賜教2024-01-01

