0

0

ArrayDeque作为双端队列的使用方法

P粉602998670

P粉602998670

发布时间:2025-09-17 16:12:01

|

681人浏览过

|

来源于php中文网

原创

ArrayDeque是Java中高效的双端队列实现,基于数组实现,支持在两端高效添加和移除元素,性能优于LinkedList,适用于栈和队列场景。它具备均摊O(1)的时间复杂度,内存连续,缓存友好,常用于BFS、LRU缓存、回文检查等场景,但不支持null元素且非线程安全,使用时应优先通过Deque接口声明,必要时选择并发替代方案。

arraydeque作为双端队列的使用方法

ArrayDeque,作为Java集合框架中一个相当实用的双端队列实现,它的核心价值在于提供了一个可以在两端高效地添加和移除元素的动态数组。简单来说,它既能当用(后进先出),也能当队列用(先进先出),而且在大多数操作上,它的性能表现都非常出色,通常比LinkedList作为双端队列要快。

解决方案

使用ArrayDeque作为双端队列其实非常直观。你首先需要实例化一个ArrayDeque对象,然后就可以利用它提供的方法在队列的两端进行操作了。

import java.util.ArrayDeque;
import java.util.Deque;

public class ArrayDequeUsage {
    public static void main(String[] args) {
        // 声明时通常使用Deque接口,这是一种好的编程习惯
        Deque names = new ArrayDeque<>();

        // 在队尾添加元素 (作为队列的入队操作)
        names.addLast("Alice"); // 等同于 names.add("Alice"); 或 names.offer("Alice");
        names.offer("Bob");

        // 在队头添加元素 (作为栈的入栈操作)
        names.addFirst("Charlie");
        names.push("David"); // push方法是Deque接口特有的,语义上更明确为“入栈”

        System.out.println("当前队列内容: " + names); // 输出: [David, Charlie, Alice, Bob]

        // 从队头移除元素 (作为队列的出队操作)
        String firstElement = names.removeFirst(); // 等同于 names.remove(); 或 names.poll();
        System.out.println("移除队头元素: " + firstElement); // David
        System.out.println("移除后队列: " + names); // [Charlie, Alice, Bob]

        // 从队尾移除元素 (作为栈的出栈操作)
        String lastElement = names.removeLast(); // 等同于 names.pop();
        System.out.println("移除队尾元素: " + lastElement); // Bob
        System.out.println("移除后队列: " + names); // [Charlie, Alice]

        // 查看队头元素但不移除
        String peekFirst = names.peekFirst(); // 等同于 names.peek();
        System.out.println("查看队头元素: " + peekFirst); // Charlie

        // 查看队尾元素但不移除
        String peekLast = names.peekLast();
        System.out.println("查看队尾元素: " + peekLast); // Alice

        // 检查队列是否为空
        System.out.println("队列是否为空? " + names.isEmpty()); // false

        // 获取队列大小
        System.out.println("队列大小: " + names.size()); // 2
    }
}

这段代码展示了ArrayDeque作为双端队列的基本操作。你会发现,它提供了一系列方法,有的名称更偏向队列(如

addLast
/
offer
removeFirst
/
poll
),有的则更偏向栈(如
push
pop
)。这种灵活性正是其魅力所在。

ArrayDeque与LinkedList作为双端队列的性能差异和选择考量

在Java里,除了ArrayDeque,LinkedList也能实现Deque接口,作为双端队列来用。那么,什么时候用哪个呢?坦白说,对于纯粹的双端队列操作,ArrayDeque几乎总是更好的选择

ArrayDeque底层是基于数组实现的,它在内存中是连续存放的。这意味着在访问元素时,CPU缓存的命中率会很高,这对于性能来说是个巨大的优势。它的添加和移除操作(在两端)都是均摊O(1)的时间复杂度。为什么是均摊呢?因为它在内部数组满的时候需要扩容,扩容操作是O(n)的,但这种操作不常发生,所以平均下来还是非常高效的。

而LinkedList呢,它是一个双向链表。每个元素(节点)都包含数据以及指向前一个和后一个节点的引用。虽然在链表的两端添加和移除元素也是O(1),但它的内存是非连续的。这意味着每次访问元素时,都可能需要跳到内存的不同位置,这会降低CPU缓存的效率。此外,每个节点都需要额外的内存来存储前后引用,所以LinkedList的内存开销通常会比ArrayDeque大一些。

所以,我的建议是:

  • 优先选择ArrayDeque:如果你只需要一个高效的双端队列,并且不需要在队列中间频繁插入或删除元素(因为这会让LinkedList的O(1)优势发挥不出来),ArrayDeque是首选。它性能更好,内存效率也更高。
  • 考虑LinkedList的情况:只有当你需要处理
    null
    元素(ArrayDeque不允许)或者确实需要在队列的任意位置进行高效的插入和删除操作时(虽然这已经超出了“双端队列”的范畴,更像是一个通用链表的使用场景),才应该考虑LinkedList。

我个人在实际项目中,只要是用到栈或队列,几乎都是无脑ArrayDeque,因为它在绝大多数场景下都能提供令人满意的性能。

ArrayDeque在实际开发中的应用场景举例

ArrayDeque的用途非常广泛,因为它兼具栈和队列的特性,使得它在处理各种数据结构和算法问题时都游刃有余。

一个非常经典的场景是广度优先搜索 (BFS)。在图或树的遍历中,BFS需要一个队列来存储待访问的节点。每访问一个节点,就将其所有未访问的邻居节点入队。ArrayDeque在这里就完美扮演了队列的角色:

addLast()
用于入队,
removeFirst()
用于出队。

深蓝企业网站管理系统1
深蓝企业网站管理系统1

本程序版权归作者所有不得利用本程序从事任何非法活动!本程序功能有限只能满足基础型企业网站的建站需求,无法满足更搞要求的企业站,也无法利用本程序制作门户网站,更不能建站购物站。为了克服以上技术局限,我们开发了“新坐标CMS-超级云端网站管理系统”,可以满足任何要求的企业网站,也可以制作购物网站,同时还可以制作门户型网站。其标签式调用方法让您随心所欲调用想要的结果。 使用说明:根目录包含netbox无

下载
// 伪代码示例:BFS遍历
// Deque queue = new ArrayDeque<>();
// queue.addLast(startNode);
// while (!queue.isEmpty()) {
//     Node current = queue.removeFirst();
//     // 处理current节点
//     // 将current的未访问邻居节点添加到queue.addLast()
// }

另一个非常实用的场景是实现最近最少使用 (LRU) 缓存。LRU缓存的核心思想是:当缓存空间不足时,淘汰掉最近最少使用的那个数据。这通常可以通过结合

HashMap
ArrayDeque
(或者更常见的
LinkedHashMap
,它内部已经实现了类似逻辑)来实现。ArrayDeque可以用来维护一个访问顺序:每当一个元素被访问,就把它移到队列的队尾(表示最近使用过);当需要淘汰时,就从队头移除元素(表示最久未使用)。

// 伪代码示例:LRU缓存的访问顺序维护
// Map cacheMap = new HashMap<>();
// Deque accessOrder = new ArrayDeque<>(); // 队头是LRU,队尾是MRU

// void put(Key k, Value v) {
//     if (cacheMap.containsKey(k)) {
//         accessOrder.remove(k); // 移除旧位置
//     } else if (cacheMap.size() >= CAPACITY) {
//         Key lruKey = accessOrder.removeFirst(); // 淘汰最旧的
//         cacheMap.remove(lruKey);
//     }
//     cacheMap.put(k, v);
//     accessOrder.addLast(k); // 标记为最近使用
// }

// Value get(Key k) {
//     if (cacheMap.containsKey(k)) {
//         accessOrder.remove(k); // 移除旧位置
//         accessOrder.addLast(k); // 标记为最近使用
//         return cacheMap.get(k);
//     }
//     return null;
// }

此外,它还可以用于:

  • 回文检查:将字符串字符依次入队,然后从两端同时取出字符进行比较。
  • 滑动窗口最大/最小值问题:维护一个单调队列(通常用ArrayDeque实现),存储窗口内元素的索引,队头始终是当前窗口的最大/最小值。
  • 表达式求值:在处理逆波兰表达式或中缀转后缀时,栈是必不可少的,ArrayDeque可以高效地扮演这个角色。

这些场景都体现了ArrayDeque在需要高效地从两端操作数据时的强大能力。

使用ArrayDeque时可能遇到的常见陷阱与最佳实践

尽管ArrayDeque非常强大,但在使用时还是有一些需要注意的地方,避免踩坑。

首先,一个非常重要的点是ArrayDeque不是线程安全的。如果你在多线程环境下使用ArrayDeque,并且有多个线程同时对其进行读写操作,那么就可能会出现数据不一致的问题。在这种情况下,你需要自己进行外部同步(例如使用

Collections.synchronizedDeque()
包装,或者手动加锁),或者考虑使用
java.util.concurrent.ConcurrentLinkedDeque
,它是专门为并发环境设计的双端队列。我个人在写多线程代码时,如果需要并发队列,会直接选择
ConcurrentLinkedDeque
,省去自己同步的麻烦。

其次,ArrayDeque不允许存储

null
元素。如果你尝试
addFirst(null)
addLast(null)
,它会直接抛出
NullPointerException
。这一点与LinkedList不同,LinkedList是允许存储null的。所以在处理可能包含null的数据时,要特别小心,最好在入队前进行null检查。

再者,关于初始容量。ArrayDeque在创建时可以指定一个初始容量。虽然它会自动扩容,但如果你能预估大致的元素数量,提供一个合适的初始容量可以减少扩容的次数,从而在一定程度上提升性能。例如,

new ArrayDeque<>(100)
。不过,对于大多数应用来说,默认容量(通常是16)也足够了,扩容的开销通常可以忽略不计。过度优化初始容量,反而可能增加代码的复杂性。

最后,一个最佳实践是始终使用

Deque
接口来声明你的ArrayDeque对象,例如
Deque names = new ArrayDeque<>();
。这是一种面向接口编程的好习惯。它让你的代码更加灵活,如果未来你需要切换到其他Deque实现(比如ConcurrentLinkedDeque),修改起来会更方便,也提高了代码的可读性和可维护性。

总的来说,ArrayDeque是一个非常值得信赖且高效的数据结构。理解它的底层原理和特性,并注意上述的几个点,就能在实际开发中充分发挥它的优势。

热门AI工具

更多
DeepSeek
DeepSeek

幻方量化公司旗下的开源大模型平台

豆包大模型
豆包大模型

字节跳动自主研发的一系列大型语言模型

通义千问
通义千问

阿里巴巴推出的全能AI助手

腾讯元宝
腾讯元宝

腾讯混元平台推出的AI助手

文心一言
文心一言

文心一言是百度开发的AI聊天机器人,通过对话可以生成各种形式的内容。

讯飞写作
讯飞写作

基于讯飞星火大模型的AI写作工具,可以快速生成新闻稿件、品宣文案、工作总结、心得体会等各种文文稿

即梦AI
即梦AI

一站式AI创作平台,免费AI图片和视频生成。

ChatGPT
ChatGPT

最最强大的AI聊天机器人程序,ChatGPT不单是聊天机器人,还能进行撰写邮件、视频脚本、文案、翻译、代码等任务。

相关专题

更多
string转int
string转int

在编程中,我们经常会遇到需要将字符串(str)转换为整数(int)的情况。这可能是因为我们需要对字符串进行数值计算,或者需要将用户输入的字符串转换为整数进行处理。php中文网给大家带来了相关的教程以及文章,欢迎大家前来学习阅读。

443

2023.08.02

c语言中null和NULL的区别
c语言中null和NULL的区别

c语言中null和NULL的区别是:null是C语言中的一个宏定义,通常用来表示一个空指针,可以用于初始化指针变量,或者在条件语句中判断指针是否为空;NULL是C语言中的一个预定义常量,通常用来表示一个空值,用于表示一个空的指针、空的指针数组或者空的结构体指针。

236

2023.09.22

java中null的用法
java中null的用法

在Java中,null表示一个引用类型的变量不指向任何对象。可以将null赋值给任何引用类型的变量,包括类、接口、数组、字符串等。想了解更多null的相关内容,可以阅读本专题下面的文章。

438

2024.03.01

js 字符串转数组
js 字符串转数组

js字符串转数组的方法:1、使用“split()”方法;2、使用“Array.from()”方法;3、使用for循环遍历;4、使用“Array.split()”方法。本专题为大家提供js字符串转数组的相关的文章、下载、课程内容,供大家免费下载体验。

298

2023.08.03

js截取字符串的方法
js截取字符串的方法

js截取字符串的方法有substring()方法、substr()方法、slice()方法、split()方法和slice()方法。本专题为大家提供字符串相关的文章、下载、课程内容,供大家免费下载体验。

212

2023.09.04

java基础知识汇总
java基础知识汇总

java基础知识有Java的历史和特点、Java的开发环境、Java的基本数据类型、变量和常量、运算符和表达式、控制语句、数组和字符串等等知识点。想要知道更多关于java基础知识的朋友,请阅读本专题下面的的有关文章,欢迎大家来php中文网学习。

1500

2023.10.24

字符串介绍
字符串介绍

字符串是一种数据类型,它可以是任何文本,包括字母、数字、符号等。字符串可以由不同的字符组成,例如空格、标点符号、数字等。在编程中,字符串通常用引号括起来,如单引号、双引号或反引号。想了解更多字符串的相关内容,可以阅读本专题下面的文章。

623

2023.11.24

java读取文件转成字符串的方法
java读取文件转成字符串的方法

Java8引入了新的文件I/O API,使用java.nio.file.Files类读取文件内容更加方便。对于较旧版本的Java,可以使用java.io.FileReader和java.io.BufferedReader来读取文件。在这些方法中,你需要将文件路径替换为你的实际文件路径,并且可能需要处理可能的IOException异常。想了解更多java的相关内容,可以阅读本专题下面的文章。

613

2024.03.22

Python 自然语言处理(NLP)基础与实战
Python 自然语言处理(NLP)基础与实战

本专题系统讲解 Python 在自然语言处理(NLP)领域的基础方法与实战应用,涵盖文本预处理(分词、去停用词)、词性标注、命名实体识别、关键词提取、情感分析,以及常用 NLP 库(NLTK、spaCy)的核心用法。通过真实文本案例,帮助学习者掌握 使用 Python 进行文本分析与语言数据处理的完整流程,适用于内容分析、舆情监测与智能文本应用场景。

10

2026.01.27

热门下载

更多
网站特效
/
网站源码
/
网站素材
/
前端模板

精品课程

更多
相关推荐
/
热门推荐
/
最新课程
Kotlin 教程
Kotlin 教程

共23课时 | 2.9万人学习

C# 教程
C# 教程

共94课时 | 7.8万人学习

Java 教程
Java 教程

共578课时 | 52.2万人学习

关于我们 免责申明 举报中心 意见反馈 讲师合作 广告合作 最新更新
php中文网:公益在线php培训,帮助PHP学习者快速成长!
关注服务号 技术交流群
PHP中文网订阅号
每天精选资源文章推送

Copyright 2014-2026 https://www.php.cn/ All Rights Reserved | php.cn | 湘ICP备2023035733号