前言
ArrayDeque 是 Java 集合框架中的一个双端队列(Double-Ended Queue),它实现了 Deque 接口,支持从队列的两端高效地添加和移除元素。它的底层实现基于动态数组,具有较高的性能,适用于需要频繁操作队列头尾的场景。以下从源码逐步解析其实现原理。
源码分析
数据结构
ArrayDeque 是一个基于循环数组(circular array)实现的双端队列。与普通的数组不同,它的头指针(head)和尾指针(tail)可以在数组中循环移动,从而避免了频繁的数组拷贝操作。源码中核心字段如下:
| 1 | public class ArrayDeque<E> extends AbstractCollection<E> | 
- elements:存储元素的数组,默认初始容量为 16(稍后会分析)。
- head:指向队列头部的位置。
- tail:指向队列尾部下一个可插入的位置。
- transient:表示这些字段不会被序列化,因为- ArrayDeque自定义了序列化逻辑。
构造方法
ArrayDeque 提供了三种构造方法:
| 1 | // 无参构造,默认容量为 16 | 
allocateElements 方法
这个方法用于初始化数组容量,值得注意的是,容量必须是 2 的幂次方:
| 1 | private void allocateElements(int numElements) { | 
- 如果指定的 numElements小于 8,则使用默认最小容量 8。
- 如果大于等于 8,则通过位运算计算出大于等于 numElements的最小 2 的幂次方。
- 为什么要用 2 的幂次方?因为循环数组的索引计算依赖模运算,而 2 的幂次方可以用位与(&)操作高效实现,避免除法运算。
核心方法
添加元素
ArrayDeque 支持从头部或尾部添加元素,主要方法包括 addFirst 和 addLast。
addLast 方法(尾部添加)
| 1 | public void addLast(E e) { | 
- 将元素放入 tail位置。
- 更新 tail:(tail + 1) & (elements.length - 1)是循环数组的关键,通过位与操作实现模运算。
- 如果 tail追上head,说明数组已满,调用doubleCapacity扩容。
addFirst 方法(头部添加)
| 1 | public void addFirst(E e) { | 
- head前移一位:- (head - 1) & (elements.length - 1),同样利用位运算实现循环。
- 如果 head和tail重合,触发扩容。
扩容机制
当数组满时,调用 doubleCapacity 方法将容量翻倍:
| 1 | private void doubleCapacity() { | 
- 新容量是旧容量的两倍(n << 1)。
- 将原数组的元素按顺序拷贝到新数组,调整后 head指向 0,tail指向新插入位置。
- 扩容后,队列从数组开头连续排列,便于后续操作。
移除元素
ArrayDeque 支持从头部或尾部移除元素,例如 pollFirst 和 pollLast。
pollFirst 方法(移除头部)
| 1 | public E pollFirst() { | 
- 返回 head位置的元素,并将其置为null。
- head前进一位,依然通过位运算实现循环。
pollLast 方法(移除尾部)
| 1 | public E pollLast() { | 
tail 后退一位,移除最后一个元素。
后记
- 时间复杂度:- 添加(addFirst、addLast):$O(1)$(均摊)
- 删除(pollFirst、pollLast):$O(1)$
- 扩容:$O(n)$,但均摊后为 $O(1)$。
- 空间复杂度:$O(n)$,$n$ 为元素个数。
 
- 添加(
优势:相比 LinkedList,ArrayDeque 使用连续内存,缓存友好,性能更高。
限制:不支持随机访问,不适合需要索引操作的场景。
ArrayDeque 不允许插入 null,否则抛出 NullPointerException。ArrayDeque 线程不安全,需要外部同步机制(如 Collections.synchronizedDeque)。
