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