导读:本期聚焦于小伙伴创作的《怎么利用 ArrayDeque 实现一个比 Stack 和 LinkedList 性能更优的内存栈结构》,敬请观看详情。把栈结构建在 Stack 或 LinkedList 之上,往往会在高并发推送和弹出场景下暴露出不必要的开销。Stack 继承自 Vector,所有方法都加了重量级同步锁,单线程里纯属浪费;LinkedList 每个节点都要额外维护前后指针,内存占用和缓存命中都不理想。ArrayDeque 基于可扩容循环数组实现,头尾操作都是 O(1) 且无需加锁,非常适合做内存栈。本文从底层存储差异讲起,给出用 ArrayDeque 封装栈的代码示例,并对比三者在吞吐与 GC 上的表现,帮你用更少资源支撑更高频次入栈出栈。

在 Java 中实现一个内存栈,很多资料会提到 Stack 类和用 LinkedList 模拟栈,但从存储结构和运行时开销来看,这两者都不是最优解。ArrayDeque 基于循环数组,避免了链表节点的额外指针开销,也没有 Stack 那种全局同步锁,用它能写出一个更轻量、更快的内存栈。

怎么利用 ArrayDeque 实现一个比 Stack 和 LinkedList 性能更优的内存栈结构

为什么 Stack 和 LinkedList 不适合做高性能栈

Stack 是 Java 早期集合框架中的类,它继承自 Vector。Vector 内部对每个公开方法都使用了 synchronized 修饰,以保证线程安全。在单线程或者已经由外部控制并发的场景中,这种同步完全是多余的开销,会让每次 push 和 pop 都陷入锁竞争的准备状态,即使没有真正发生竞争,也会拖慢方法调用。

LinkedList 通过双向链表实现,每个元素被包装成 Node 对象,里面除了保存值,还要维护 prev 和 next 两个引用。大量入栈时会产生很多小对象,增加垃圾回收压力,而且链表节点在内存中分布不连续,CPU 缓存命中率低。对于只需要在同一端进出的栈来说,维护双向链接属于功能冗余。

ArrayDeque 的底层结构优势

ArrayDeque 使用一个普通的 Object 数组来存储元素,并通过两个 int 类型的头尾指针 head 和 tail 在数组中形成一个逻辑上的循环区间。当元素从头部加入或从尾部取出时,只需要移动指针并赋值数组下标,不需要创建节点对象,也不需要加锁。数组的连续内存布局对 CPU 缓存非常友好。

当数组装满时,ArrayDeque 会按比例扩容并拷贝数据到新数组,这种操作发生在容量临界处,平摊到每次操作上仍然是 O(1)。相比 LinkedList 持续产生新节点,ArrayDeque 在内存分配上更克制,长时间运行下 GC 更平稳。

用 ArrayDeque 封装一个内存栈

虽然 ArrayDeque 本身提供了 addFirst、pollFirst 等方法,但从语义上我们更希望暴露 push 和 pop。下面代码展示如何做一个简单的栈封装,并附带初始容量设置以减少早期扩容。

import java.util.ArrayDeque;

// 基于 ArrayDeque 的内存栈封装
public class MemoryStack<T> {
    private final ArrayDeque<T> deque;

    // 指定初始容量,降低扩容频率
    public MemoryStack(int initialCapacity) {
        deque = new ArrayDeque<>(initialCapacity);
    }

    // 入栈,放在队列头部
    public void push(T item) {
        deque.addFirst(item);
    }

    // 出栈,从队列头部取
    public T pop() {
        return deque.pollFirst();
    }

    // 查看栈顶但不移除
    public T peek() {
        return deque.peekFirst();
    }

    // 当前栈大小
    public int size() {
        return deque.size();
    }
}

上面的代码把 ArrayDeque 的头部当作栈顶,addFirst 对应入栈,pollFirst 对应出栈。如果在多线程环境下使用,可以在外部加一把 ReentrantLock,而不是像 Stack 那样每个方法都内置锁,这样锁的粒度更可控。

如果担心扩容带来的短暂停顿,可以在创建栈时根据业务峰值预估容量。例如预期最多同时压入十万元素,就直接传入 131072 这类 2 的幂次初始值,ArrayDeque 会以该值作为数组长度起点。

三种实现方式对比

我们用一张表列出三者在关键维度上的差异,方便在做技术选型时快速判断。

实现方式底层结构线程安全额外内存单线程吞吐
Stack数组加同步锁内置锁
LinkedList双向链表不安全高(节点对象)
ArrayDeque循环数组不安全

从表中可以看出,ArrayDeque 在单线程吞吐和内存占用上都优于另外两者。如果你的系统对延迟敏感且栈操作非常频繁,比如做表达式解析、深度优先遍历或者临时对象池,换成 ArrayDeque 封装的栈往往能观察到明显的性能提升。

需要提醒的是,ArrayDeque 不允许插入 null 元素,而 Stack 和 LinkedList 可以。在把原有基于 LinkedList 的栈迁移过来时,要检查业务代码里是否依赖了 null 入栈,如果有,需要改成特殊标记对象或者提前做非空校验。

简单的压测示例

下面这段程序在同一个 JVM 里分别用三种结构做百万次入栈出栈,虽然具体耗时随机器变化,但相对关系通常稳定。

import java.util.ArrayDeque;
import java.util.LinkedList;
import java.util.Stack;

public class StackBenchmark {
    static int COUNT = 1_000_000;

    public static void main(String[] args) {
        // Stack 测试
        Stack<Integer> stack = new Stack<>();
        long t1 = System.nanoTime();
        for (int i = 0; i < COUNT; i++) stack.push(i);
        while (!stack.isEmpty()) stack.pop();
        System.out.println("Stack 耗时: " + (System.nanoTime() - t1));

        // LinkedList 测试
        LinkedList<Integer> list = new LinkedList<>();
        long t2 = System.nanoTime();
        for (int i = 0; i < COUNT; i++) list.push(i);
        while (!list.isEmpty()) list.pop();
        System.out.println("LinkedList 耗时: " + (System.nanoTime() - t2));

        // ArrayDeque 测试
        ArrayDeque<Integer> deque = new ArrayDeque<>(COUNT);
        long t3 = System.nanoTime();
        for (int i = 0; i < COUNT; i++) deque.addFirst(i);
        while (!deque.isEmpty()) deque.pollFirst();
        System.out.println("ArrayDeque 耗时: " + (System.nanoTime() - t3));
    }
}

运行后一般会看到 ArrayDeque 的耗时明显小于 Stack,也经常优于 LinkedList,尤其在设置合理初始容量后,数组扩容次数减少,差距更明显。这个基准虽然简单,但足以说明在纯内存栈场景下,ArrayDeque 是更划算的底层选择。

总结来说,实现高性能内存栈并不需要自己写数组和指针,直接复用 ArrayDeque 并做一层语义封装即可。它规避了 Stack 的同步浪费和 LinkedList 的节点开销,在绝大多数单线程或外部加锁的场景里,都是比传统方案更优的替代品。

ArrayDequeStackLinkedList修改时间:2026-08-06 18:15:46

免责声明:​ 已尽一切努力确保本网站所含信息的准确性。网站内容多为原创整理与精心编撰,观点力求客观中立。本站旨在免费分享,内容仅供个人学习、研究或参考使用。若引用了第三方作品,版权归原作者所有。如内容涉及您的权益,请联系我们处理。
内容垂直聚焦
专注技术核心技术栏目,确保每篇文章深度聚焦于实用技能。从代码技巧到架构设计,为用户提供无干扰的纯技术知识沉淀,精准满足专业提升需求。
知识结构清晰
覆盖从开发到部署的全链路。AI、前端、编程、数据库、服务器、建站、系统层层递进,构建清晰学习路径,帮助用户系统化掌握开发与运维所需的核心技术。
深度技术解析
拒绝泛泛而谈,深入技术细节与实践难点。无论是数据库优化还是服务器配置,均结合真实场景与代码示例进行剖析,致力于提供可直接应用于工作的解决方案。
专业领域覆盖
精准对应开发生命周期。从前端界面到后端编程,从数据库操作到服务器运维,形成完整闭环,一站式满足全栈工程师和运维人员的技术需求。
即学即用高效
内容强调实操性,步骤清晰、代码完整。用户可根据教程直接复现和应用于自身项目,显著缩短从学习到实践的距离,快速解决开发中的具体问题。
持续更新保障
专注既定技术方向进行长期、稳定的内容输出。确保各栏目技术文章持续更新迭代,紧跟主流技术发展趋势,为用户提供经久不衰的学习价值。