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

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

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

