首页 > Java > java教程 > 正文

Java中ArrayDeque的核心使用方法

P粉602998670
发布: 2025-09-19 15:23:01
原创
885人浏览过
ArrayDeque在Java中基于可变数组实现,支持高效双端操作,适合作为栈(用push/pop/peek)和队列(用offer/poll/peek)使用,内存紧凑、性能优越;相比LinkedList,其内存局部性更好、迭代更快,但扩容时有O(n)开销;推荐优先使用push/pop/peek模拟栈,避免add/remove抛异常,选用offer/poll处理队列更安全,并预估初始容量以减少扩容开销。

java中arraydeque的核心使用方法

ArrayDeque在Java中是一个非常实用的数据结构,它实现了Deque接口,可以高效地作为(LIFO,后进先出)和队列(FIFO,先进先出)来使用。它的核心优势在于基于可变数组实现,这意味着在两端添加和移除元素都非常迅速,通常接近O(1)的时间复杂度,并且相比于链表实现的Deque,它在内存使用上更为紧凑,避免了每个节点额外的对象开销。

解决方案

ArrayDeque作为Java集合框架中的一员,其核心使用方法围绕着双端队列的特性展开。它既能模拟栈的行为,也能模拟队列的行为,这使得它在多种算法和日常编程任务中都非常得心应手。

作为栈使用时: ArrayDeque提供了

push()
登录后复制
pop()
登录后复制
peek()
登录后复制
方法,这些方法与
java.util.Stack
登录后复制
类中的方法功能一致,但ArrayDeque通常被认为是一个更好的栈实现选择,因为它没有
Stack
登录后复制
类继承自
Vector
登录后复制
带来的同步开销。

  • push(E e)
    登录后复制
    : 将元素e添加到双端队列的头部(栈顶)。
  • pop()
    登录后复制
    : 移除并返回双端队列头部的元素(栈顶元素)。如果队列为空,则抛出
    NoSuchElementException
    登录后复制
  • peek()
    登录后复制
    : 返回双端队列头部的元素(栈顶元素),但不移除。如果队列为空,则返回
    null
    登录后复制

作为队列使用时: ArrayDeque提供了

offer()
登录后复制
poll()
登录后复制
peek()
登录后复制
方法,这些方法与
Queue
登录后复制
接口中的方法功能一致。它在需要高性能队列的场景下表现出色。

  • offer(E e)
    登录后复制
    : 将元素e添加到双端队列的尾部(队尾)。
  • poll()
    登录后复制
    : 移除并返回双端队列头部的元素(队头元素)。如果队列为空,则返回
    null
    登录后复制
  • peek()
    登录后复制
    : 返回双端队列头部的元素(队头元素),但不移除。如果队列为空,则返回
    null
    登录后复制

此外,ArrayDeque也提供了

addFirst()
登录后复制
,
addLast()
登录后复制
,
removeFirst()
登录后复制
,
removeLast()
登录后复制
,
getFirst()
登录后复制
,
getLast()
登录后复制
等方法,它们与
push()
登录后复制
,
offer()
登录后复制
,
pop()
登录后复制
,
poll()
登录后复制
等功能类似,但在语义上更强调双端操作。需要注意的是,
add()
登录后复制
remove()
登录后复制
方法在队列为空时会抛出异常,而
offer()
登录后复制
poll()
登录后复制
则返回特殊值(
false
登录后复制
null
登录后复制
),这在处理不确定队列状态时更为灵活。

import java.util.ArrayDeque;

public class ArrayDequeExample {
    public static void main(String[] args) {
        // 作为栈使用
        System.out.println("--- 作为栈使用 ---");
        ArrayDeque<String> stack = new ArrayDeque<>();
        stack.push("A"); // 栈顶
        stack.push("B");
        stack.push("C"); // 新栈顶

        System.out.println("栈顶元素 (peek): " + stack.peek()); // 输出 C
        System.out.println("弹出元素 (pop): " + stack.pop());   // 输出 C
        System.out.println("弹出元素 (pop): " + stack.pop());   // 输出 B
        System.out.println("当前栈: " + stack); // 输出 [A]

        // 作为队列使用
        System.out.println("\n--- 作为队列使用 ---");
        ArrayDeque<String> queue = new ArrayDeque<>();
        queue.offer("X"); // 队尾
        queue.offer("Y");
        queue.offer("Z"); // 新队尾

        System.out.println("队头元素 (peek): " + queue.peek()); // 输出 X
        System.out.println("出队元素 (poll): " + queue.poll());   // 输出 X
        System.out.println("出队元素 (poll): " + queue.poll());   // 输出 Y
        System.out.println("当前队列: " + queue); // 输出 [Z]
    }
}
登录后复制

ArrayDeque与LinkedList在作为双端队列使用时,性能差异体现在哪里?

在我个人的开发经验中,选择ArrayDeque还是LinkedList作为双端队列,这往往取决于具体的应用场景和对性能的细微要求。它们虽然都实现了Deque接口,但底层实现机制的差异,决定了它们在不同操作上的性能表现。

立即学习Java免费学习笔记(深入)”;

首先,底层数据结构是根本区别。ArrayDeque基于动态数组实现,而LinkedList则基于双向链表。这意味着ArrayDeque的数据在内存中是连续存放的,而LinkedList的每个元素都是一个独立的节点对象,包含数据本身以及指向前后节点的引用。

这种差异直接导致了内存效率的不同。ArrayDeque通常在内存使用上更紧凑。LinkedList的每个节点除了存储实际数据外,还需要额外的空间来存储

next
登录后复制
prev
登录后复制
指针。对于存储大量小对象的场景,LinkedList的这种额外开销会非常显著。ArrayDeque虽然在扩容时可能需要进行数组拷贝,但其整体内存局部性更好,这在现代CPU缓存体系结构下,通常能带来更好的性能。

两端操作(添加/移除)方面,两者都能实现接近O(1)的时间复杂度。ArrayDeque通过维护头尾指针,可以在数组的两端高效地添加或移除元素。但需要注意的是,当数组空间不足时,ArrayDeque会进行扩容,这涉及到一个新的更大数组的分配和旧数组元素的拷贝,这个操作的复杂度是O(n)。LinkedList则每次操作都涉及创建或销毁一个节点,并调整前后节点的引用,这同样是O(1)的,且没有扩容的隐患。所以,如果你的应用场景是频繁地在两端进行大量操作,并且对性能的稳定性有较高要求,LinkedList在极端情况下可能表现得更稳定一些,因为它没有ArrayDeque那种潜在的扩容开销。然而,实际情况中,ArrayDeque的扩容机制通常优化得很好,分摊下来,其平均性能依然非常优秀。

中间操作(例如在队列中间插入或删除元素)对两者来说都不是高效的。ArrayDeque需要移动大量元素,复杂度是O(n)。LinkedList虽然理论上可以通过遍历找到中间位置,然后进行O(1)的插入/删除,但寻找中间位置本身就是O(n)的,所以总体上也是O(n)。但话说回来,双端队列的设计初衷就不是为了高效地进行中间操作。

迭代性能上,ArrayDeque通常会优于LinkedList。由于ArrayDeque的数据是连续存储的,遍历时CPU的缓存命中率会更高。LinkedList在遍历时需要跳跃访问内存中的不同节点,这可能会导致更多的缓存未命中。

总的来说,如果我对性能有要求,并且操作主要集中在队列的两端,我会优先考虑ArrayDeque。它的内存效率和迭代性能通常更优。只有在需要频繁在中间进行操作(尽管这不符合Deque的典型用法),或者对内存分配的稳定性有极致要求时,我才会考虑LinkedList。

ArrayDeque在作为栈(Stack)使用时,有哪些推荐的最佳实践?

将ArrayDeque作为栈来使用,是我个人非常推崇的做法,因为它比Java标准库提供的

java.util.Stack
登录后复制
类有更好的性能和更清晰的设计。在使用ArrayDeque模拟栈行为时,有一些实践经验可以分享:

百度文心百中
百度文心百中

百度大模型语义搜索体验中心

百度文心百中 22
查看详情 百度文心百中

一个核心的推荐是优先使用

push()
登录后复制
pop()
登录后复制
peek()
登录后复制
这组方法
。这些方法是专门为栈语义设计的,它们让代码的意图更加明确,易于理解。
push(E e)
登录后复制
将元素压入栈顶,
pop()
登录后复制
将栈顶元素弹出,
peek()
登录后复制
查看栈顶元素但不弹出。

尽管ArrayDeque也提供了

addFirst()
登录后复制
removeFirst()
登录后复制
getFirst()
登录后复制
等方法,它们在功能上与栈方法等价,但从语义清晰度的角度来看,使用
push/pop/peek
登录后复制
更能直接表达“这是一个栈”的概念,这对于代码的可读性和维护性至关重要。

关于空栈处理,这是一个需要特别注意的地方。

pop()
登录后复制
peek()
登录后复制
方法在栈为空时,会抛出
NoSuchElementException
登录后复制
。在实际应用中,尤其是在处理用户输入或外部数据时,栈是否为空往往是不可预测的。因此,在调用这些方法之前,通常需要通过
isEmpty()
登录后复制
方法进行检查,或者选择使用
pollFirst()
登录后复制
(对于栈来说,就是
pop()
登录后复制
的非抛异常版本)和
peekFirst()
登录后复制
(对于栈来说,就是
peek()
登录后复制
的非抛异常版本),它们在栈为空时会返回
null
登录后复制
,而不是抛出异常。这在很多场景下能让代码更加健壮。

// 示例:使用ArrayDeque进行括号匹配
import java.util.ArrayDeque;
import java.util.HashMap;
import java.util.Map;

public class BracketMatcher {
    public boolean isValid(String s) {
        ArrayDeque<Character> stack = new ArrayDeque<>();
        Map<Character, Character> mappings = new HashMap<>();
        mappings.put(')', '(');
        mappings.put('}', '{');
        mappings.put(']', '[');

        for (char c : s.toCharArray()) {
            if (mappings.containsKey(c)) { // 是闭括号
                char topElement = stack.isEmpty() ? '#' : stack.pop(); // 如果栈为空,则用'#'占位,避免空指针
                if (topElement != mappings.get(c)) {
                    return false;
                }
            } else { // 是开括号
                stack.push(c);
            }
        }
        return stack.isEmpty(); // 所有括号都匹配成功,栈应为空
    }

    public static void main(String[] args) {
        BracketMatcher matcher = new BracketMatcher();
        System.out.println("()[]{} is valid: " + matcher.isValid("()[]{}")); // true
        System.out.println("([{}]) is valid: " + matcher.isValid("([{}])")); // true
        System.out.println("({[}) is valid: " + matcher.isValid("({[})")); // false
        System.out.println("(] is valid: " + matcher.isValid("(]"));       // false
        System.out.println("{ is valid: " + matcher.isValid("{"));         // false
    }
}
登录后复制

在这个括号匹配的例子中,我们清晰地使用了

push()
登录后复制
pop()
登录后复制
来模拟栈的行为。在
pop()
登录后复制
之前,通过
isEmpty()
登录后复制
检查栈状态,是处理潜在
NoSuchElementException
登录后复制
的一种有效方式。

最后,初始容量的预估。ArrayDeque在创建时可以指定一个初始容量。如果能大致预估栈中将要存储的元素数量,在构造函数中提供一个合适的初始容量,可以减少后续因扩容而产生的数组拷贝开销,从而在一定程度上优化性能。例如:

ArrayDeque<Integer> stack = new ArrayDeque<>(100);
登录后复制
。当然,即使不指定,ArrayDeque的自动扩容机制也已经非常高效了。

ArrayDeque在作为队列(Queue)使用时,如何避免常见的陷阱?

将ArrayDeque作为队列来使用,同样非常高效且常见,尤其是在实现广度优先搜索(BFS)或任务调度等场景。然而,在使用过程中,一些常见的陷阱需要注意,以确保代码的健壮性和正确性。

一个主要的陷阱是混淆不同入队/出队方法的行为差异。ArrayDeque实现了Deque接口,所以它提供了多种添加和移除元素的方法。

  • 入队操作
    add(E e)
    登录后复制
    offer(E e)
    登录后复制
    addLast(E e)
    登录后复制
    offerLast(E e)
    登录后复制
    • add(E e)
      登录后复制
      addLast(E e)
      登录后复制
      :如果队列容量受限(尽管ArrayDeque通常是无界的),添加失败会抛出
      IllegalStateException
      登录后复制
      。对于ArrayDeque来说,这通常意味着内存耗尽。
    • offer(E e)
      登录后复制
      offerLast(E e)
      登录后复制
      :添加失败时返回
      false
      登录后复制
      ,不会抛出异常。这通常是更推荐的队列入队方式,因为它允许你优雅地处理添加失败的情况(尽管对于ArrayDeque,这很少发生)。
  • 出队操作
    remove()
    登录后复制
    poll()
    登录后复制
    removeFirst()
    登录后复制
    pollFirst()
    登录后复制
    • remove()
      登录后复制
      removeFirst()
      登录后复制
      :如果队列为空,会抛出
      NoSuchElementException
      登录后复制
    • poll()
      登录后复制
      pollFirst()
      登录后复制
      :如果队列为空,会返回
      null
      登录后复制
      。这通常是更推荐的队列出队方式,因为它避免了在队列可能为空时抛出异常的风险。

因此,最佳实践是优先使用

offer()
登录后复制
poll()
登录后复制
方法
。它们提供了更温和的错误处理机制,通过返回值来指示操作结果,而不是通过异常中断程序流程。这对于构建健壮的系统至关重要,尤其是在处理并发或外部输入时。

另一个需要注意的方面是ArrayDeque的容量特性。ArrayDeque在逻辑上是一个无界队列,它会根据需要自动扩容。这意味着你不需要担心队列“满”的问题(除非系统内存耗尽)。但如果你的应用场景确实需要一个有固定容量限制的队列,并且在队列满时需要阻塞或拒绝新的元素,那么ArrayDeque就不适合了。在这种情况下,你可能需要考虑

java.util.concurrent
登录后复制
包下的
ArrayBlockingQueue
登录后复制
或其他并发队列实现。

遍历队列时,也存在一些需要留意的点。使用迭代器或增强for循环遍历ArrayDeque是安全的。然而,在遍历过程中修改队列(例如添加或移除元素,除了通过迭代器自身的

remove()
登录后复制
方法)会导致
ConcurrentModificationException
登录后复制
。这在所有非线程安全的集合中都是一个常见的问题。如果你需要在遍历时修改队列,通常的做法是先收集需要修改的元素,或者使用迭代器提供的方法。

// 示例:使用ArrayDeque进行广度优先搜索(BFS)
import java.util.ArrayDeque;
import java.util.HashSet;
import java.util.Set;

public class BFSExample {
    // 假设这是一个简单的图,用邻接列表表示
    // 这里为了简化,直接用字符串表示节点,并手动构建邻居关系
    private static Map<String, String[]> graph = new HashMap<>();
    static {
        graph.put("A", new String[]{"B", "C"});
        graph.put("B", new String[]{"A", "D", "E"});
        graph.put("C", new String[]{"A", "F"});
        graph.put("D", new String[]{"B"});
        graph.put("E", new String[]{"B", "F"});
        graph.put("F", new String[]{"C", "E"});
    }

    public void bfs(String startNode) {
        ArrayDeque<String> queue = new ArrayDeque<>();
        Set<String> visited = new HashSet<>();

        queue.offer(startNode);
        visited.add(startNode);

        System.out.println("BFS Traversal starting from " + startNode + ":");

        while (!queue.isEmpty()) {
            String currentNode = queue.poll(); // 安全地从队列头部取出元素
            System.out.print(currentNode + " ");

            // 获取当前节点的邻居
            String[] neighbors = graph.get(currentNode);
            if (neighbors != null) {
                for (String neighbor : neighbors) {
                    if (!visited.contains(neighbor)) {
                        visited.add(neighbor);
                        queue.offer(neighbor); // 安全地将邻居添加到队列尾部
                    }
                }
            }
        }
        System.out.println();
    }

    public static void main(String[] args) {
        BFSExample bfs = new BFSExample();
        bfs.bfs("A"); // Output: A B C D E F
    }
}
登录后复制

在这个BFS示例中,我们始终使用

offer()
登录后复制
poll()
登录后复制
方法来操作队列,这使得代码在处理队列为空或添加元素时更为健壮,避免了不必要的异常。这也是在实际项目中,我个人在处理队列时最常用的模式。

以上就是Java中ArrayDeque的核心使用方法的详细内容,更多请关注php中文网其它相关文章!

最佳 Windows 性能的顶级免费优化软件
最佳 Windows 性能的顶级免费优化软件

每个人都需要一台速度更快、更稳定的 PC。随着时间的推移,垃圾文件、旧注册表数据和不必要的后台进程会占用资源并降低性能。幸运的是,许多工具可以让 Windows 保持平稳运行。

下载
来源:php中文网
本文内容由网友自发贡献,版权归原作者所有,本站不承担相应法律责任。如您发现有涉嫌抄袭侵权的内容,请联系admin@php.cn
最新问题
开源免费商场系统广告
热门教程
更多>
最新下载
更多>
网站特效
网站源码
网站素材
前端模板
关于我们 免责申明 举报中心 意见反馈 讲师合作 广告合作 最新更新 English
php中文网:公益在线php培训,帮助PHP学习者快速成长!
关注服务号 技术交流群
PHP中文网订阅号
每天精选资源文章推送
PHP中文网APP
随时随地碎片化学习

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