ArrayDeque

ℹ️定位说明

Queue 系列第 1 篇详篇。官方推荐的栈/队列实现:循环数组,头尾操作均摊 O(1),比 LinkedList 快(无节点分配、内存连续)。本篇含循环数组的扩容与下标回绕机制。

ArrayDeque:高性能双端队列

一、基本概念

ArrayDeque 是 Java 提供的一种基于动态数组实现的双端队列(Deque),支持高效的栈(LIFO)与队列(FIFO)操作,是 Deque 接口的一个重要实现类。

public class ArrayDeque<E> extends AbstractCollection<E>

        implements Deque<E>, Cloneable, Serializable

它具备以下特点:

特性 说明
底层结构 循环数组(动态扩容)
支持双端操作 支持从头尾插入和删除
非线程安全 多线程下需手动同步
不允许 null 元素 防止和空返回值歧义
相比 LinkedList 更高效 访问局部性更好,GC 压力更小,操作更快

二、底层结构与数据模型

ArrayDeque 使用一个循环数组 elements[] 来存储数据,并用两个指针标记头尾:

  • head: 指向队首元素位置
  • tail: 指向下一个插入队尾的位置

数组逻辑结构示意:

    tail


[ _ _ B C D _ _ A ]


       head

插入或删除时通过环形偏移计算位置,从而实现 O(1) 的效率。

三、添加元素 —— offerFirst / offerLast

public void addFirst(E e) {

    if (e == null) throw new NullPointerException();

    elements[head = (head - 1) & (elements.length - 1)] = e;

    if (head == tail) resize();

}
public void addLast(E e) {

    if (e == null) throw new NullPointerException();

    elements[tail] = e;

    tail = (tail + 1) & (elements.length - 1);

    if (tail == head) resize();

}

说明:

  • 采用位运算 (index ± 1) & (length - 1) 代替取模 %,更高效(前提是数组长度为 2 的幂)
  • 判断队满:插入后 head == tail,说明已满,触发扩容
  • 插入前不允许为 null,防止与 poll() 返回 null 混淆

四、删除元素 —— pollFirst / pollLast

public E pollFirst() {

    int h = head;

    E result = elements[h];

    if (result == null) return null;

    elements[h] = null; // 清除引用,便于 GC

    head = (h + 1) & (elements.length - 1);

    return result;

}
public E pollLast() {

    int t = (tail - 1) & (elements.length - 1);

    E result = elements[t];

    if (result == null) return null;

    elements[t] = null;

    tail = t;

    return result;

}

说明:

  • 删除后指针推进,释放数组槽位,避免内存泄漏
  • 仍采用环形偏移保证索引安全

五、扩容机制 —— resize()

扩容触发时,ArrayDeque 会将旧数据从 head 开始顺序复制到新数组中:

private void resize() {

    int oldCapacity = elements.length;

    int newCapacity = oldCapacity << 1; // 扩容为原来的2倍

    Object[] newArray = new Object[newCapacity];

    int r = oldCapacity - head; // 从head到数组末尾的元素个数

    System.arraycopy(elements, head, newArray, 0, r);

    System.arraycopy(elements, 0, newArray, r, head);

    elements = newArray;

    head = 0;

    tail = oldCapacity;

}

核心要点:

  • 扩容为原数组的 2 倍(比 ArrayList 的 1.5 倍更保守)
  • 采用双段拷贝的方式解决循环结构问题
  • 拷贝代价较大,建议合理初始化容量

六、典型应用场景

场景 示例
栈(LIFO) push() / pop()
队列(FIFO) offerLast() / pollFirst()
双端队列 支持头尾任意插入删除
缓存/滑动窗口 固定容量 + 头部淘汰策略实现

七、和 LinkedList 的对比

维度 ArrayDeque LinkedList
底层结构 循环数组 双向链表
头尾插入删除效率 O(1) O(1)
中间插入删除 不支持 O(n)
内存占用 紧凑(数组) 高(每个节点有两个引用)
是否支持 null ❌(抛出异常)
线程安全

推荐:一般建议使用 ArrayDeque 替代 LinkedList 作为栈或队列的实现。

八、示例代码

Deque<String> deque = new ArrayDeque<>();

// 队列用法

deque.offerLast("a");

deque.offerLast("b");

System.out.println(deque.pollFirst()); // 输出 a

// 栈用法

deque.push("x");

deque.push("y");

System.out.println(deque.pop()); // 输出 y

九、注意事项总结

  • 不允许存储 null 元素
  • 非线程安全,需外部加锁或使用并发容器(如 ConcurrentLinkedDeque)
  • 不支持元素随机访问(无索引)
  • 插入删除性能优越,推荐作为双端队列使用

⬅️ 00-Queue和Deque总览 🏠 00-Java ➡️ PriorityQueue