应使用 ArrayDeque 而非 Stack 类,因其性能更优、设计更符合栈语义;ArrayDeque 底层为循环数组,均摊 O(1),无同步开销,且需用 Deque 接口声明、避免混用双端方法。

为什么不用 Stack 类,而推荐 ArrayDeque
Java 的 Stack 类是遗留类,继承自 Vector,所有操作都加了同步锁,性能差;更关键的是它违背了“栈只应暴露 push/pop/peek”的设计原则——Stack 还能用 get(0)、remove(2) 等随机访问方法,一不小心就写出非栈逻辑。官方文档明确建议用 Deque 实现栈,而 ArrayDeque 是其中最常用、性能最好的实现。
常见错误现象:Stack 在多线程下看似“安全”,但实际同步粒度太粗,且现代并发场景早该用 ConcurrentLinkedDeque 或显式锁;还有人用 LinkedList 当栈,它虽然实现了 Deque,但每次 push 都新建节点,内存开销和 GC 压力比 ArrayDeque 高得多。
-
ArrayDeque底层是循环数组,push/pop均摊时间复杂度 O(1),无同步开销 - 必须用
Deque接口变量声明,而非ArrayDeque具体类型,便于替换和测试 - 不要调用
addFirst/removeFirst等双端队列方法——它们语义不清,应统一使用栈专用方法
push 和 pop 的正确写法与空栈陷阱
ArrayDeque 作为栈用时,push 等价于 addFirst,pop 等价于 removeFirst,二者都操作队头(即逻辑栈顶)。但 pop 在栈为空时抛 NoSuchElementException,不是 NullPointerException 或返回 null —— 这点容易被忽略,导致线上崩溃。
典型使用场景:表达式求值、括号匹配、DFS 递归转迭代。这时你总得先检查是否为空再 pop,或者用 peek 预判。
立即学习“Java免费学习笔记(深入)”;
- 安全写法:先
if (!stack.isEmpty()) { stack.pop(); },别依赖 try-catch 捕获NoSuchElementException来控制流程 -
peek返回栈顶元素但不移除,为空时返回null(注意:如果栈存的是可能为null的引用类型,需额外判断) - 避免混用:不要在同一个栈实例上交替调用
push和addLast,会破坏栈序
ArrayDeque 容量增长机制与初始容量设置
ArrayDeque 默认构造容量是 16,每次扩容为原容量的 2 倍(不是 1.5 倍),且要求容量始终为 2 的幂。扩容涉及数组复制,虽均摊 O(1),但突发大量 push 仍可能引发短暂停顿。如果你大概知道栈的最大深度(比如解析嵌套 JSON 不超过 1000 层),直接指定初始容量更稳。
性能影响:小栈(10k)且频繁扩容时,GC 压力和内存碎片会上升。
- 初始化写法:
Deque<integer> stack = new ArrayDeque(2048);</integer>,传入的值会被自动提升到最近的 2 的幂(如 2048 就是,2000 会被提升为 2048) - 不能传 0 或负数,否则抛
IllegalArgumentException - 别用
new ArrayDeque()后立刻ensureCapacity——它没有这个方法,扩容完全由内部触发
泛型擦除下如何处理原始类型与 null 值
ArrayDeque 是泛型类,但 Java 泛型运行时擦除,所以它底层存储的是 Object[]。这意味着:你无法直接存 int,必须用 Integer;若业务逻辑允许栈中出现 null(比如表示占位符),peek 返回 null 就无法区分“栈空”和“栈顶是 null”。
常见错误:把 int 往 Deque<integer></integer> 里 push,看似正常,实则每次自动装箱,高频操作下对象创建开销明显;或用 if (stack.peek() == null) 判断空栈,结果栈非空但顶是 null,逻辑错乱。
- 原始类型优先用专门容器(如
IntStack第三方库),或自己封装一层避免重复装箱 - 若必须支持
null元素,改用isEmpty()判断栈空,而不是依赖peek()返回值 - 别在栈里存可变对象(如
ArrayList)后还外部修改它——栈内引用没变,但内容已不同,容易引发隐蔽 bug
真正难的不是写对那几行 push/pop,而是想清楚:这个栈会不会跨线程共享?要不要支持 null?最大深度有没有界?这些决定才真正影响选型和容错设计。










