登录
推荐 文章 Go 技术 课程 下载 专题 AI
首页 >  文章 >  java教程

Java ArrayDeque 怎样实现双端队列的首尾操作

来源:17golang原创

时间:2026-10-20 00:45:27 312浏览 收藏

我第一次把双端队列接进任务调度代码时,真正容易写错的不是 new ArrayDeque(),而是“这一端到底是谁”和“空了以后要异常还是要特殊值”。先给结论:ArrayDeque 用 first 表示头部、last 表示尾部;需要明确失败就用 add/remove/element 这一组,需要把空队列当成普通分支就用 offer/poll/peek。它不允许存入 null,也不是线程安全容器。

官方地址:https://docs.oracle.com/en/java/javase/25/docs/api/java.base/java/util/ArrayDeque.html

下面的示例只讨论首尾操作和边界,不把 ArrayDeque 包装成共享并发队列,也不把静态说明图当成运行截图。

先把 first 和 last 分清楚

ArrayDeque 是 Deque 的可扩容数组实现。对一个普通队列,可以把头部理解为下一次取出的方向,把尾部理解为新元素通常追加的方向;对双端队列,两边都能插入、删除和查看。我的经验是先在变量旁写清“头部/尾部”,再选方法名,避免只凭 add 和 remove 猜方向。

ArrayDeque 与 Deque 以及 first 头部和 last 尾部 API 的静态结构说明图
图1:结构说明图,展示容器、头部、尾部与首尾方法的静态对应关系;不是运行截图。

需要从头部放入元素时,用 addFirst(e);从尾部放入时,用 addLast(e)。删除对应使用 removeFirst() 和 removeLast(),只查看而不删除则使用 peekFirst() 和 peekLast()。这些名字比把队列当作一条没有方向的容器更适合维护。

插入、删除、查看:先决定失败语义

Queue 和 Deque 的一组方法有意分成两种风格:一组失败时抛异常,另一组返回特殊值。ArrayDeque 没有固定容量限制,所以插入通常不会因为“满了”而失败,但方法成对语义仍然值得保留。

目的明确失败普通分支
插入头部addFirstofferFirst
删除头部removeFirstpollFirst
查看头部getFirstpeekFirst
队列尾部addoffer
删除队头removepoll
查看队头elementpeek

例如,空队列可能是业务允许的“没有待处理任务”,就可以用 poll() 或 peek() 让调用方判断 null。如果空队列说明程序状态已经不对,remove() 或 element() 抛出的 NoSuchElementException 会更早暴露问题。

ArrayDeque 中异常版、特殊值版和栈式别名 API 的静态关系说明图
图2:关系说明图,比较异常版、特殊值版和栈式别名的边界;不是运行结果截图。

用代码把首尾选择写出来

下面把两个常见意图放在一起:任务队列从尾部加入、从头部取出;撤销栈则只使用头部。注释只解释关键语义,避免把每一行都写成注释。

import java.util.ArrayDeque;

public class ArrayDequeEnds {
    public static void main(String[] args) {
        var tasks = new ArrayDeque();
        // 尾部追加新任务,头部取出最早进入的任务
        tasks.addLast("编译");
        tasks.addLast("打包");
        String next = tasks.pollFirst();
        System.out.println(next); // 预期示例输出:编译

        var undo = new ArrayDeque();
        // push 等价于 addFirst,适合把同一端当作栈顶
        undo.push("改配置");
        undo.push("改代码");
        String latest = undo.pop();
        System.out.println(latest); // 预期示例输出:改代码

        // peek 只查看,不删除;空队列时返回 null
        String waiting = tasks.peekFirst();
        if (waiting == null) {
            System.out.println("没有待处理任务");
        }
    }
}

push(e) 是把元素放到头部的栈式表达,pop() 则从头部删除并返回。若读者看到的是“撤销最近一次操作”,push/pop 比 addFirst/removeFirst 更能表达意图;若读者看到的是“双端数据结构”,首尾方法名更直接。

空队列、null 和共享访问要单独处理

ArrayDeque 禁止 null 元素,这反而让 poll、peek 返回 null 时具有明确含义:队列为空,而不是取出了一个合法的 null。不要为了绕过判断把 null 当作占位元素塞进去。

另外,ArrayDeque 没有内部同步。单线程任务循环、局部的请求处理和明确由一个所有者管理的工作队列都可以考虑它;多个线程同时修改同一个实例时,不能仅因为类型名里有 Queue 就认为它提供并发安全。需要跨线程传递任务时,应另行选择并发队列或在外部建立同步边界。

我会在代码评审里检查三件事:调用方是否已经决定空队列是异常还是分支;方法名是否和数据方向一致;容器是否被多个线程共享。三项都清楚,ArrayDeque 的 API 基本不会再靠记忆猜。

相关问题

  • ArrayDeque 的 add 和 offer 怎么选? 两者都表示插入;当失败应作为异常暴露时用 add,需要用返回值表达插入失败时用 offer。
  • poll 和 remove 的区别是什么? 两者都会删除并返回队头;空队列时 poll 返回 null,remove 抛出 NoSuchElementException。
  • ArrayDeque 能不能存 null? 不能。空队列返回 null 的约定也依赖这个限制。
  • push 和 addFirst 有什么关系? push 是栈语义的头部插入别名;选择它还是 addFirst,取决于你想表达栈还是双端队列。
声明:本文转载于:17golang原创 如有侵犯,请联系study_golang@163.com删除
相关阅读
更多>
最新阅读
更多>
课程推荐
更多>