Java ArrayDeque
是基于动态循环数组的双端队列(Deque)实现,支持在队列头部和尾部进行高效的插入、删除操作。它不仅可以作为普通队列(FIFO)使用,还能作为栈(LIFO)使用,且在多数场景下性能优于 LinkedList
。本文将从核心结构、关键操作、设计模式等维度,深入解析其源码实现。
一、类定义与核心属性
1.1 类继承关系
public class ArrayDeque<E> extends AbstractCollection<E> implements Deque<E>, Cloneable, Serializable
ArrayDeque
继承自AbstractCollection
(提供集合基础操作),实现了Deque
接口(双端队列规范),支持克隆和序列化。
1.2 核心属性
transient Object[] elements; // 存储元素的数组(长度始终为 2 的幂次)
transient int head; // 队列头部索引(下一个弹出元素的位置)
transient int tail; // 队列尾部索引(下一个插入元素的位置)
private static final int MIN_INITIAL_CAPACITY = 8; // 最小初始容量
- 循环数组:通过
head
和tail
指针实现“循环”效果。当head
或tail
到达数组末尾时,通过位运算& (elements.length - 1)
回绕到数组头部(例如,数组长度为 16 时,15 + 1 & 15
结果为 0)。 - 容量特性:数组长度始终是 2 的幂次(如 8、16、32…),这是为了优化索引计算(位运算替代取模)。
二、循环数组的核心操作
2.1 插入元素:addFirst(E e)
与 addLast(E e)
插入操作支持在队列头部(addFirst
)或尾部(addLast
)插入元素,核心逻辑是调整 head
或 tail
指针,并在数组满时扩容。
addFirst
源码:
public void addFirst(E e) {
if (e == null) throw new NullPointerException(); // 不允许 null 元素
elements[head = (head - 1) & (elements.length - 1)] = e; // 头部前一个位置插入
if (head == tail) doubleCapacity(); // 数组满,扩容
}
- 索引计算:
head = (head - 1) & (elements.length - 1)
等价于(head - 1) % elements.length
,但位运算更高效。例如,若head=0
,数组长度为 16,则新head
为 15((0-1) & 15 = 15
)。 - 扩容触发:当
head
和tail
重合时(数组满),调用doubleCapacity()
扩容。
addLast
源码:
public void addLast(E e) {
if (e == null) throw new NullPointerException();
elements[tail] = e; // 尾部位置插入
if ((tail = (tail + 1) & (elements.length - 1)) == head) // 尾部后移,检查是否满
doubleCapacity();
}
- 索引计算:
tail = (tail + 1) & (elements.length - 1)
实现尾部指针循环后移。
2.2 删除元素:pollFirst()
与 pollLast()
删除操作从头部或尾部弹出元素,核心是获取并清空对应位置的元素,调整指针。
pollFirst
源码:
public E pollFirst() {
int h = head;
@SuppressWarnings("unchecked") E result = (E) elements[h];
if (result != null) { // 队列非空
elements[h] = null; // 清空头部位置
head = (h + 1) & (elements.length - 1); // 头部后移
}
return result;
}
pollLast
源码:
public E pollLast() {
int t = (tail - 1) & (elements.length - 1); // 尾部前一个位置
@SuppressWarnings("unchecked") E result = (E) elements[t];
if (result != null) {
elements[t] = null; // 清空尾部位置
tail = t; // 尾部前移
}
return result;
}
三、扩容机制:doubleCapacity()
当数组满(head == tail
)时,触发扩容,容量翻倍。
源码实现:
private void doubleCapacity() {
assert head == tail; // 仅在数组满时调用
int n = elements.length;
int newCapacity = n << 1; // 容量翻倍(2 的幂次特性保持)
if (newCapacity < 0) throw new IllegalStateException("Deque too big");
Object[] a = new Object[newCapacity];
// 迁移元素:原数组从 head 到末尾的部分,复制到新数组头部;原数组头部到 tail 的部分,复制到新数组后续位置
System.arraycopy(elements, head, a, 0, n - head);
System.arraycopy(elements, 0, a, n - head, head);
elements = a;
head = 0; // 新数组头部重置为 0
tail = n; // 新数组尾部为原容量位置(即新容量的一半)
}
- 元素迁移:原数组被拆分为两部分(
[head, 原数组末尾]
和[0, head-1]
),复制到新数组的连续位置,保持循环特性。
四、初始化与容量计算
构造函数支持指定初始容量或从集合初始化,核心通过 allocateElements
和 calculateSize
确保数组长度为 2 的幂次。
calculateSize
源码:
private static int calculateSize(int numElements) {
int initialCapacity = MIN_INITIAL_CAPACITY;
if (numElements >= initialCapacity) {
initialCapacity = numElements;
initialCapacity |= initialCapacity >>> 1; // 填充低位为 1
initialCapacity |= initialCapacity >>> 2;
initialCapacity |= initialCapacity >>> 4;
initialCapacity |= initialCapacity >>> 8;
initialCapacity |= initialCapacity >>> 16;
initialCapacity++; // 得到下一个 2 的幂次
if (initialCapacity < 0) initialCapacity >>>= 1; // 处理溢出
}
return initialCapacity;
}
- 位运算填充:通过右移和按位或操作,将
numElements
转换为大于等于其值的最小 2 的幂次(例如,numElements=10
会转换为 16)。
五、设计模式应用:迭代器模式
ArrayDeque
通过内部类 DeqIterator
(正向迭代器)和 DescendingIterator
(反向迭代器)实现 Iterator
接口,符合迭代器模式的设计思想:将遍历逻辑封装在迭代器中,客户端无需关心集合内部结构。
5.1 正向迭代器 DeqIterator
private class DeqIterator implements Iterator<E> {
private int cursor = head; // 当前遍历位置(初始为 head)
private int fence = tail; // 遍历边界(初始为 tail)
private int lastRet = -1; // 最后一次访问的索引
public boolean hasNext() { return cursor != fence; }
public E next() {
if (cursor == fence) throw new NoSuchElementException();
E result = (E) elements[cursor];
if (tail != fence || result == null) // 快速失败检查
throw new ConcurrentModificationException();
lastRet = cursor;
cursor = (cursor + 1) & (elements.length - 1); // 循环后移
return result;
}
}
- 快速失败(
Fail-Fast
):通过比较tail
和初始fence
(构造时的 tail),检测集合是否被修改(如插入/删除元素),若不一致则抛出ConcurrentModificationException
。
5.2 反向迭代器 DescendingIterator
反向迭代器从 tail
向前遍历到 head
,逻辑与正向迭代器类似,但索引计算方向相反:
private class DescendingIterator implements Iterator<E> {
private int cursor = tail; // 当前遍历位置(初始为 tail)
private int fence = head; // 遍历边界(初始为 head)
public E next() {
cursor = (cursor - 1) & (elements.length - 1); // 循环前移
E result = (E) elements[cursor];
if (head != fence || result == null) // 快速失败检查
throw new ConcurrentModificationException();
return result;
}
}
六、关键特性总结
- 高效的双端操作:插入、删除的时间复杂度为
O(1)
(均摊),优于LinkedList
的指针操作。 - 循环数组设计:通过位运算实现索引循环,避免数组复制的高开销(仅在扩容时复制)。
- 容量优化:数组长度始终为 2 的幂次,位运算替代取模,提升索引计算效率。
- 线程不安全:未实现同步,多线程环境需外部加锁或使用
ConcurrentLinkedDeque
。 - 不允许 null 元素:插入
null
会直接抛出NullPointerException
(与LinkedList
不同)。
总结
ArrayDeque
是 Java 集合框架中高效的双端队列实现,核心通过循环数组和位运算优化实现了 O(1)
时间的双端操作。其迭代器模式的应用,将遍历逻辑封装在独立类中,保证了集合结构的封装性。在需要双端队列的场景(如 BFS 遍历、任务队列),ArrayDeque
是优于 LinkedList
的选择,但需注意其非线程安全的特性。