ArrayDeque 缓存命中率更高因其底层为连续内存的循环数组,而 LinkedList 的 Node 对象分散在堆中导致频繁缓存未命中;实测 10 万次栈操作 ArrayDeque 耗时约 3ms,LinkedList 约 15ms。
直接用
替代
做栈,就能显著提升 CPU 缓存命中率——根本原因在于前者是连续内存块,后者是散落在堆里的独立
对象。
为什么 ArrayDeque 的缓存命中率更高
CPU 缓存对连续内存访问极其友好。
底层是循环数组,所有元素在内存中紧挨着存放;每次
或
只需更新头索引(
),读写都在同一缓存行附近。而
每次
都要
,每个
含
、
、
三个字段,对象地址随机分布在堆中,一次栈操作可能触发多次缓存未命中(Cache Miss)。
实测显示:10 万次压栈+弹栈,
耗时约 3ms,
约 15ms——差的不是算法复杂度,是缓存行为。
替换时必须注意的三个行为差异
和
虽都实现
接口,但语义和限制不同:
不允许
元素,插入
会抛
;
允许
初始容量为 16,且扩容后容量始终是 2 的幂(如 16 → 32 → 64);
无容量概念,每增一个元素就新建一个对象
栈操作应统一用
/
,不要混用
/
——虽等价,但
/
语义更清晰,且 JDK 文档明确将其定位为栈方法
如何避免扩容抖动影响性能稳定性
虽然
扩容是倍增策略(均摊 O(1)),但单次扩容仍需
复制整个数组。高频短生命周期栈(如解析器递归、DFS 临时栈)若不预估大小,可能在关键路径上触发意外扩容。
建议:
若栈深可预估(例如语法树深度上限为 200),构造时指定初始容量:
避免在循环内反复创建小栈;复用实例比每次都
更省 GC
注意:不能调用
(
没这个方法),只能靠构造参数控制起始容量
实际栈操作代码对比
以下是最简替换示例,重点看方法名和 null 处理:
对比
写法:
真正容易被忽略的是:即使你没显式插入
,某些业务逻辑(如 Optional.orElse(null) 后直接入栈)可能悄悄引入它,导致迁移到
后突然报错——这不是 bug,是设计约束的提前暴露。
ArrayDequeLinkedListNodeArrayDequepush()pop()headLinkedListpush()new Node()NodeitemnextprevArrayDequeLinkedListArrayDequeLinkedListDequeArrayDequenullnullNullPointerExceptionLinkedListArrayDequeLinkedListpush()pop()addFirst()removeFirst()pushpopArrayDequeSystem.arraycopynew ArrayDeque(256)newensureCapacity()ArrayDequeDeque stack = new ArrayDeque<>(); // ✅ 推荐
// stack.push(null); // ❌ 运行时报 NullPointerException
stack.push("a");
stack.push("b");
String top = stack.pop(); // "b"
String next = stack.pop(); // "a" LinkedListDeque stack = new LinkedList<>(); // ⚠️ 允许 null,但慢
stack.push("a");
stack.push(null); // ✅ 合法,但带来额外 null 检查开销 nullArrayDeque