Java優(yōu)先隊(duì)列PriorityQueue超全解析
一、基本定義與特性
- 本質(zhì):PriorityQueue 是 Java 集合框架中 Queue 接口的實(shí)現(xiàn)類(lèi),基于堆(Heap) 數(shù)據(jù)結(jié)構(gòu)實(shí)現(xiàn)(默認(rèn)是最小堆),底層通過(guò)數(shù)組存儲(chǔ)元素。
- 核心特性:
- 隊(duì)列中的元素會(huì)按照優(yōu)先級(jí)順序出隊(duì),而非先進(jìn)先出(FIFO);
- 不允許存儲(chǔ)
null元素; - 非線程安全,多線程場(chǎng)景需使用
PriorityBlockingQueue; - 容量可自動(dòng)擴(kuò)容(默認(rèn)初始容量為 11,擴(kuò)容規(guī)則:容量 < 64 時(shí)翻倍,≥64 時(shí)擴(kuò)容 50%);
- 迭代器遍歷的結(jié)果不保證有序,僅出隊(duì)(
poll()/remove())時(shí)保證優(yōu)先級(jí)順序。
二、關(guān)于堆
| 堆的核心維度 | 具體內(nèi)容 |
| 定義 | 完全二叉樹(shù)結(jié)構(gòu),滿(mǎn)足 “堆序性”:- 最小堆:任意節(jié)點(diǎn)值 ≤ 其子節(jié)點(diǎn)值(根節(jié)點(diǎn)為全局最小值);- 最大堆:任意節(jié)點(diǎn)值 ≥ 其子節(jié)點(diǎn)值(根節(jié)點(diǎn)為全局最大值)。 |
| 存儲(chǔ)結(jié)構(gòu) | 基于數(shù)組實(shí)現(xiàn)(利用完全二叉樹(shù)的特性):- 根節(jié)點(diǎn):數(shù)組索引 0;- 索引 i 的節(jié)點(diǎn) → 左子節(jié)點(diǎn) 2i+1、右子節(jié)點(diǎn) 2i+2、父節(jié)點(diǎn) (i-1)/2(整數(shù)除法);- 無(wú)需連續(xù)存儲(chǔ)空節(jié)點(diǎn),空間利用率高。 |
| 核心操作 | 1. 插入(siftUp / 向上調(diào)整):新元素插入數(shù)組尾部,從下往上對(duì)比父節(jié)點(diǎn),不滿(mǎn)足堆序性則交換,直到堆序性恢復(fù)(時(shí)間復(fù)雜度 O (log n));2. 刪除堆頂(siftDown / 向下調(diào)整):移除根節(jié)點(diǎn),將數(shù)組最后一個(gè)元素移到根節(jié)點(diǎn),從上往下對(duì)比子節(jié)點(diǎn),不滿(mǎn)足堆序性則交換(選更小 / 更大的子節(jié)點(diǎn)),直到堆序性恢復(fù)(時(shí)間復(fù)雜度 O (log n));3. 堆化(heapify):將無(wú)序數(shù)組轉(zhuǎn)換為堆結(jié)構(gòu),從最后一個(gè)非葉子節(jié)點(diǎn)(索引 size/2 - 1)開(kāi)始,逐個(gè)執(zhí)行 siftDown(時(shí)間復(fù)雜度 O (n))。 |
| 特性 | - 僅能高效獲取 / 刪除堆頂元素(優(yōu)先級(jí)最高);- 遍歷無(wú)序(完全二叉樹(shù)的特性決定,僅堆頂有序);- 支持動(dòng)態(tài)擴(kuò)容(數(shù)組滿(mǎn)時(shí)擴(kuò)容,規(guī)則由實(shí)現(xiàn)決定)。 |
1.優(yōu)先級(jí)規(guī)則 ↔ 堆的類(lèi)型
- PriorityQueue默認(rèn)實(shí)現(xiàn)最小堆:依賴(lài)元素的
Comparable接口(compareTo()定義自然順序),本質(zhì)是維護(hù)最小堆的堆序性; - 自定義優(yōu)先級(jí)(如最大堆):傳入
Comparator比較器,本質(zhì)是修改堆的 “堆序性判斷邏輯”,例如:
// 最大堆:Comparator修改siftUp/siftDown的比較規(guī)則,讓父節(jié)點(diǎn)≥子節(jié)點(diǎn) PriorityQueue<Integer> maxHeap = new PriorityQueue<>((a, b) -> b - a);
- 異常根源:若元素未實(shí)現(xiàn)
Comparable且無(wú)Comparator,堆序性無(wú)法判斷,運(yùn)行時(shí)拋ClassCastException。
2. 存儲(chǔ)結(jié)構(gòu) ↔ 堆的數(shù)組存儲(chǔ)
- PriorityQueue 底層直接復(fù)用堆的數(shù)組存儲(chǔ)邏輯:
- 初始容量 11(數(shù)組初始長(zhǎng)度),擴(kuò)容規(guī)則:容量 < 64 時(shí)翻倍,≥64 時(shí)擴(kuò)容 50%(堆的數(shù)組存儲(chǔ)需要連續(xù)空間,擴(kuò)容是為了滿(mǎn)足堆的動(dòng)態(tài)插入);
- 空隊(duì)列時(shí)數(shù)組為默認(rèn)空數(shù)組,添加第一個(gè)元素時(shí)初始化長(zhǎng)度為 11。
3. 核心方法 ↔ 堆的核心操作
PriorityQueue 的所有核心方法,本質(zhì)都是調(diào)用堆的插入 / 刪除 / 堆化操作:

堆的存儲(chǔ)是基于數(shù)組的完全二叉樹(shù)映射—— 利用完全二叉樹(shù) “層序排列無(wú)空洞” 的特性,將樹(shù)節(jié)點(diǎn)按層序順序存入數(shù)組,無(wú)需額外存儲(chǔ)指針(如鏈?zhǔn)酱鎯?chǔ)),空間效率和索引計(jì)算效率極高,這也是 Java PriorityQueue 底層選擇數(shù)組存儲(chǔ)堆的核心原因。
4.存儲(chǔ)核心原理
- 為什么選數(shù)組?堆的本質(zhì)是完全二叉樹(shù)(除最后一層外,每一層節(jié)點(diǎn)數(shù)都滿(mǎn);最后一層節(jié)點(diǎn)靠左排列),這種結(jié)構(gòu)的節(jié)點(diǎn)可以通過(guò) “層序遍歷” 的順序無(wú)間隙地填入數(shù)組,且父 / 子節(jié)點(diǎn)的索引可通過(guò)簡(jiǎn)單數(shù)學(xué)公式計(jì)算,無(wú)需像普通二叉樹(shù)那樣存儲(chǔ)左 / 右子節(jié)點(diǎn)指針,空間利用率接近 100%。
- 核心映射規(guī)則(必記)假設(shè)堆的底層數(shù)組為
queue,節(jié)點(diǎn)的數(shù)組索引為i(從 0 開(kāi)始),則:
5.可視化示例(最小堆)
以最小堆 [1, 3, 8, 5, 4] 為例,完全二叉樹(shù)結(jié)構(gòu)與數(shù)組存儲(chǔ)的對(duì)應(yīng)關(guān)系:
1 (索引0) ← 堆頂
/ \
3(1) 8(2)
/ \
5(3) 4(4)對(duì)應(yīng)的底層數(shù)組 queue = [1, 3, 8, 5, 4],驗(yàn)證映射規(guī)則:
- 索引 1(節(jié)點(diǎn) 3)的父節(jié)點(diǎn):(1-1)/2 = 0(節(jié)點(diǎn) 1);
- 索引 0(節(jié)點(diǎn) 1)的左子節(jié)點(diǎn):2*0+1=1(節(jié)點(diǎn) 3),右子節(jié)點(diǎn):2*0+2=2(節(jié)點(diǎn) 8);
- 索引 3(節(jié)點(diǎn) 5)的父節(jié)點(diǎn):(3-1)/2=1(節(jié)點(diǎn) 3),無(wú)左右子節(jié)點(diǎn)(2*3+1=7 ≥ 5)。
6.關(guān)鍵節(jié)點(diǎn)類(lèi)型的索引判斷(堆操作的基礎(chǔ))
基于數(shù)組存儲(chǔ)的特性,可快速判斷節(jié)點(diǎn)類(lèi)型,這是堆化、siftUp/siftDown 的核心依據(jù):
- 葉子節(jié)點(diǎn):索引范圍
[size/2, size-1](size為堆的元素個(gè)數(shù));示例:上述堆 size=5,葉子節(jié)點(diǎn)索引為[2,4](節(jié)點(diǎn) 8、5、4),葉子節(jié)點(diǎn)無(wú)需執(zhí)行 siftDown(無(wú)后代可比較)。 - 非葉子節(jié)點(diǎn):索引范圍
[0, size/2 - 1];示例:size=5,非葉子節(jié)點(diǎn)索引為[0,1](節(jié)點(diǎn) 1、3),堆化時(shí)僅需遍歷這些節(jié)點(diǎn)執(zhí)行 siftDown。
7.關(guān)于siftUp/siftDown
siftUp(向上調(diào)整) 和 siftDown(向下調(diào)整),二者是堆(優(yōu)先隊(duì)列底層)維護(hù)「堆序性」的核心操作
| 操作 | 觸發(fā)場(chǎng)景 | 核心目的 | 核心邏輯(以最小堆為例) |
| siftUp | 往堆中插入新元素(如 offer) | 讓新元素找到正確位置,保證堆序性 | 1. 新元素先放到數(shù)組尾部(堆的最后一個(gè)節(jié)點(diǎn));2. 不斷和父節(jié)點(diǎn)比較,若更小則交換;3. 直到滿(mǎn)足「父≤子」或到根節(jié)點(diǎn)。 |
| siftDown | 1. 刪除堆頂元素(如 poll);2. 堆化(heapify) | 讓替換到堆頂 / 非葉子節(jié)點(diǎn)的元素歸位,保證堆序性 | 1. 從當(dāng)前節(jié)點(diǎn)(堆頂 / 非葉子節(jié)點(diǎn))開(kāi)始;2. 不斷和左右子節(jié)點(diǎn)比較,選更小的子節(jié)點(diǎn)交換;3. 直到滿(mǎn)足「父≤子」或到葉子節(jié)點(diǎn)。 |
總結(jié):siftUp 是「自下而上」找位置(插入用),siftDown 是「自上而下」找位置(刪堆頂 / 堆化用),最終都是為了讓堆滿(mǎn)足「最小 / 最大堆」的核心規(guī)則。
8.建堆的時(shí)間復(fù)雜度
建堆(heapify)的時(shí)間復(fù)雜度是 O(n)(而非直覺(jué)上的 O (n log n)),這是堆操作中極易混淆的關(guān)鍵點(diǎn),以下用「結(jié)論 + 原理 + 對(duì)比」講清楚:
8.1核心結(jié)論

8.2為什么堆化是 O (n)?(通俗推導(dǎo))
堆化的核心是「從最后一個(gè)非葉子節(jié)點(diǎn)反向遍歷,執(zhí)行 siftDown」,不同層級(jí)的節(jié)點(diǎn)執(zhí)行 siftDown 的次數(shù)不同,葉子節(jié)點(diǎn)甚至無(wú)需調(diào)整,整體操作次數(shù)遠(yuǎn)低于 O (n log n):
- 堆的結(jié)構(gòu)基礎(chǔ):堆是完全二叉樹(shù),假設(shè)堆的高度為
h(根節(jié)點(diǎn)層級(jí)為 0,葉子節(jié)點(diǎn)層級(jí)為h),節(jié)點(diǎn)總數(shù)n ≈ 2^(h+1) - 1; - 分層計(jì)算操作次數(shù):
- 層級(jí)
k的節(jié)點(diǎn)數(shù):2^k個(gè); - 該層級(jí)節(jié)點(diǎn)執(zhí)行 siftDown 的最大次數(shù):
h - k(越靠近根節(jié)點(diǎn),需要向下調(diào)整的次數(shù)越多;葉子節(jié)點(diǎn)層級(jí)h,調(diào)整次數(shù)為 0);
- 層級(jí)
- 總操作次數(shù)求和:總次數(shù) =
2^0*(h) + 2^1*(h-1) + 2^2*(h-2) + ... + 2^(h-1)*1;數(shù)學(xué)求和后可推導(dǎo):總次數(shù) ≈2n(收斂到 O (n))。
8.3對(duì)比理解(為什么不是 O (n log n)?)
如果用「逐個(gè) offer 元素(每次 siftUp)」的方式建堆,時(shí)間復(fù)雜度是 O (n log n):
- 每個(gè)元素插入時(shí),最多需要從葉子節(jié)點(diǎn)調(diào)整到根節(jié)點(diǎn)(最多
h=log n次操作); n個(gè)元素總操作次數(shù) =n * log n,即 O (n log n)。
而堆化用 siftDown,僅非葉子節(jié)點(diǎn)需要調(diào)整,且越靠近葉子的節(jié)點(diǎn)調(diào)整次數(shù)越少,整體效率遠(yuǎn)高于逐個(gè)插入 —— 這也是 PriorityQueue 初始化集合時(shí)選擇 heapify(而非循環(huán) offer)的核心原因。
總結(jié):堆化(O (n))是「批量?jī)?yōu)化版」建堆,利用完全二叉樹(shù)的層級(jí)特性減少無(wú)效調(diào)整;逐個(gè)插入(O (n log n))是「動(dòng)態(tài)零散版」,無(wú)批量?jī)?yōu)化,效率更低。
三、堆(PriorityQueue)常用接口 / API 全解析
Java 中堆的操作完全通過(guò) PriorityQueue 類(lèi)暴露(實(shí)現(xiàn) Queue 接口),以下是開(kāi)發(fā)中最常用的接口,按「核心操作、查詢(xún)操作、輔助操作」分類(lèi),結(jié)合堆的底層邏輯講解(默認(rèn)最小堆,最大堆僅比較器不同):
1.接口體系背景
PriorityQueue 實(shí)現(xiàn)了 java.util.Queue 接口,間接繼承 Collection/Iterable,所有接口均圍繞「堆的核心特性(堆頂為極值、O (log n) 插入 / 刪除)」設(shè)計(jì),底層關(guān)聯(lián) siftUp/siftDown 等堆操作。
2.核心操作(堆的插入 / 刪除)
| 方法簽名 | 功能說(shuō)明 | 關(guān)鍵細(xì)節(jié)(參數(shù) / 返回 / 異常 / 底層堆操作) |
| boolean offer(E e) | 向堆中插入元素(推薦使用,非阻塞) | - 參數(shù): - 返回:插入成功返回 true(堆自動(dòng)擴(kuò)容,幾乎不會(huì)返回 false); - 底層:元素插入數(shù)組尾部 → 執(zhí)行 |
| boolean add(E e) | 向堆中插入元素(推薦使用,非阻塞) | - 異常:元素為 null 拋 - 底層:同 |
| E poll() | 刪除并返回堆頂元素(優(yōu)先級(jí)最高,最小堆為最小值) | - 返回:堆為空時(shí)返回 null;非空時(shí)返回堆頂元素; - 底層:尾元素替換堆頂 → 執(zhí)行 |
| boolean remove(Object o) | 刪除堆中指定元素(若存在) | - 參數(shù): - 返回:刪除成功返回 true,失敗返回 false; - 底層:遍歷數(shù)組找元素索引(O (n))→ 尾元素替換目標(biāo)位置 → 執(zhí)行 siftUp/siftDown 調(diào)整,時(shí)間復(fù)雜度 O (n)(遍歷占主導(dǎo))。 |
3.輔助操作(堆的清空 / 遍歷)
| 方法簽名 | 功能說(shuō)明 | 關(guān)鍵細(xì)節(jié) |
| void clear() | 清空堆中所有元素 | - 底層:將數(shù)組元素置為 null,size 置 0,無(wú)堆調(diào)整,O (n)(需遍歷置空)。 |
| Iterator<E> iterator() | 獲取堆的迭代器 | - 返回:Iterator 實(shí)例; - 關(guān)鍵:迭代器遍歷的是堆的底層數(shù)組,結(jié)果無(wú)序(僅堆頂有序); - 注意:遍歷過(guò)程中修改堆(如 offer/poll)會(huì)觸發(fā)快速失敗( |
總結(jié)
到此這篇關(guān)于Java優(yōu)先隊(duì)列PriorityQueue的文章就介紹到這了,更多相關(guān)Java優(yōu)先隊(duì)列PriorityQueue內(nèi)容請(qǐng)搜索腳本之家以前的文章或繼續(xù)瀏覽下面的相關(guān)文章希望大家以后多多支持腳本之家!
- 解析Java中PriorityQueue優(yōu)先級(jí)隊(duì)列結(jié)構(gòu)的源碼及用法
- java優(yōu)先隊(duì)列PriorityQueue中Comparator的用法詳解
- Java數(shù)據(jù)結(jié)構(gòu)之優(yōu)先級(jí)隊(duì)列(PriorityQueue)用法詳解
- Java的優(yōu)先隊(duì)列PriorityQueue原理及實(shí)例分析
- Java中關(guān)于優(yōu)先隊(duì)列PriorityQueue的使用及相關(guān)方法
- Java優(yōu)先隊(duì)列(PriorityQueue)重寫(xiě)compare操作
- Java中優(yōu)先隊(duì)列PriorityQueue常用方法示例
相關(guān)文章
分析Java非阻塞算法Lock-Free的實(shí)現(xiàn)
非阻塞算法一般會(huì)使用CAS來(lái)協(xié)調(diào)線程的操作。雖然非阻塞算法有諸多優(yōu)點(diǎn),但是在實(shí)現(xiàn)上要比基于鎖的算法更加繁瑣和負(fù)責(zé)。本文將會(huì)介紹兩個(gè)是用非阻塞算法實(shí)現(xiàn)的數(shù)據(jù)結(jié)構(gòu)。2021-06-06
SpringBoot小程序推送信息的項(xiàng)目實(shí)踐
本文主要介紹了SpringBoot小程序推送信息的項(xiàng)目實(shí)踐,文中通過(guò)示例代碼介紹的非常詳細(xì),對(duì)大家的學(xué)習(xí)或者工作具有一定的參考學(xué)習(xí)價(jià)值,需要的朋友們下面隨著小編來(lái)一起學(xué)習(xí)學(xué)習(xí)吧2022-04-04
Java詳細(xì)分析String類(lèi)與StringBuffer和StringBuilder的使用方法
當(dāng)對(duì)字符串進(jìn)行修改的時(shí)候,需要使用 StringBuffer 和 StringBuilder類(lèi),和String類(lèi)不同的是,StringBuffer和 StringBuilder類(lèi)的對(duì)象能夠被多次的修改,并且不產(chǎn)生新的未使用對(duì)象2022-04-04
Java String方法獲取字符出現(xiàn)次數(shù)及字符最大相同部分示例
這篇文章主要介紹了Java String方法獲取字符出現(xiàn)次數(shù)及字符最大相同部分,涉及java字符串的遍歷、比較、計(jì)算等相關(guān)操作技巧,需要的朋友可以參考下2017-09-09
Java?獲取Zookeeper節(jié)點(diǎn)下所有數(shù)據(jù)詳細(xì)步驟
本文介紹了如何使用Java獲取ZooKeeper節(jié)點(diǎn)下所有數(shù)據(jù),實(shí)際應(yīng)用示例中,我們演示了如何從ZooKeeper節(jié)點(diǎn)下獲取配置信息并輸出到控制臺(tái),ZooKeeper是一個(gè)開(kāi)源的分布式協(xié)調(diào)服務(wù),適用于分布式系統(tǒng)中的數(shù)據(jù)同步、配置管理、命名服務(wù)等功能,感興趣的朋友一起看看吧2024-11-11
一文帶你掌握J(rèn)ava?SPI的原理和實(shí)踐
在Java中,我們經(jīng)常會(huì)提到面向接口編程,這樣減少了模塊之間的耦合,更加靈活,Java?SPI?(Service?Provider?Interface)就提供了這樣的機(jī)制,本文就來(lái)講講它的原理與具體使用吧2023-05-05
IntelliJ?idea報(bào)junit?no?tasks?available問(wèn)題的解決辦法
這篇文章主要給大家介紹了關(guān)于IntelliJ?idea報(bào)junit?no?tasks?available問(wèn)題的解決辦法,文中通過(guò)圖文介紹的非常詳細(xì),對(duì)大家的學(xué)習(xí)或者工作具有一定的參考借鑒價(jià)值,需要的朋友可以參考下2023-11-11

