跳转到主内容
趣航编程网 - 趣学编程,启航技术之路!

详解PriorityQueue的扩容算法_与ArrayList不同的步长增长规则

PriorityQueue在offer()插入新元素且底层数组已满时才触发扩容,扩容规则为:旧容量<64时新容量=2×old+2,≥64时为1.5×old;不支持缩容,也无trimToSize()方法。 PriorityQueue扩容触发时机:不是满就扩,而是offer时才检查 PriorityQueue的扩容不会在每次
offer()
前预先判断,而是在真正插入新元素、发现底层数组
queue
已满时才调用
grow()
。这点和
ArrayList
类似,但增长步长逻辑完全不同——它不按固定比例(如1.5倍),而是动态计算。 扩容只发生在
offer()
add()
等插入操作中,
poll()
peek()
完全不触发 即使队列
size() == queue.length
,也不代表立刻扩容;只有当要放第
size + 1
个元素时,才真正触发 底层数组
queue
长度可能长期大于实际元素个数(比如删掉一半后,容量不变) 扩容步长规则:旧容量<64时翻倍,≥64时加一半 Java 8+ 的
PriorityQueue.grow()
源码采用分段策略,这是和
ArrayList
最本质的区别:
ArrayList
oldCapacity + (oldCapacity >> 1)
(即1.5倍),而
PriorityQueue
是条件分支: 若当前容量
oldCap → 新容量 = oldCap * 2 + 2
(注意:不是简单×2,而是+2,所以从11→24,不是22) 若
oldCap >= 64
→ 新容量 =
oldCap + (oldCap >> 1)
(即1.5倍,和ArrayList一致) 扩容上限为
Integer.MAX_VALUE - 8
,超限会抛
OutOfMemoryError
示例验证: 初始容量11 → 插入第12个元素时扩容 →
11 * 2 + 2 = 24
容量24 → 插入第25个 →
24 * 2 + 2 = 50
容量50 → 插入第51个 →
50 * 2 + 2 = 102
(此时仍<64?不,50<64,继续走第一支) 容量102 → 已≥64 → 下次扩容 =
102 + (102 >> 1) = 102 + 51 = 153
为什么这么设计?小容量时多给点余量,避免频繁扩容 堆操作(siftUp/siftDown)对局部性敏感,频繁小步扩容会导致大量数组拷贝,拖慢
offer()
均摊性能。早期小容量阶段(<64)采用更激进的
*2+2
,是为了快速脱离“反复扩10~20个”的抖动区间。 对比
ArrayList
:从10→15→22→33→49… 扩容次数多,拷贝开销分散但总频次高
PriorityQueue
:11→24→50→102→153… 前几轮扩容幅度更大,更快进入稳定大步长区 实测:向空
PriorityQueue
连续
offer(1000)
,扩容仅约10次;同规模
ArrayList
约12~13次 容易踩的坑:自定义初始容量没用?别乱设1000 很多人以为设大初始容量能“一劳永逸”,但
PriorityQueue
的扩容阈值只看当前数组长度,和历史最大size无关。如果你设
new PriorityQueue(1000)
,但只存10个元素,它永远不缩容;反过来,如果设了11却要塞1000个,该扩还是扩,且前几轮仍走
*2+2
路径。 不要盲目设超大初始容量(如10000)——浪费内存,且首次扩容仍按规则来,不会跳过 若明确知道数据规模(如TOP-K场景只存K个),直接设
new PriorityQueue(k)
最省;若不确定,用默认11完全OK 注意:
PriorityQueue
trimToSize()
方法,无法手动收缩,长期持有大容量队列需警惕内存滞留 真正影响性能的从来不是扩容公式本身,而是你是否在循环里反复新建
PriorityQueue
却不复用——那个开销比扩容高几个数量级。

相关文章