ArrayDeque 是 Java 集合框架中一个非常重要的双端队列实现,它同时实现了 Deque 接口和 Queue 接口。以下是关于 ArrayDeque 的全面解析:
一、基本特性
1. 核心特点
-
基于循环数组:使用可调整大小的数组实现
-
双端操作:支持从队列两端高效地添加/移除元素
-
无容量限制:自动扩容(与 LinkedList 不同)
-
非线程安全:需要外部同步才能用于多线程环境
-
性能优势:
-
比 LinkedList 更节省内存(无节点开销)
-
大多数操作的时间复杂度为 O(1)
-
作为栈使用时比 Stack 类更高效
-
java.util.AbstractCollection
↑
java.util.ArrayDeque
实现的接口:
- Deque
- Queue
- Cloneable
- Serializable
二、内部实现原理
1. 核心字段
transient Object[] elements; // 存储元素的数组
transient int head; // 队首指针
transient int tail; // 队尾指针
private static final int MIN_INITIAL_CAPACITY = 8; // 最小初始容量
2. 循环数组机制
-
数组逻辑上是"环形"的,通过模运算实现循环,当指针到达数组末尾时,会从数组开头继续(通过模运算实现)
-
head 指向第一个元素,tail 指向下一个要插入的位置
-
扩容时容量总是 2 的幂次(方便使用位运算代替模运算)
-
计算索引公式:
(head + 1) & (elements.length - 1)(数组长度始终为2的幂次,可以用位运算代替模运算提高效率)
3. 扩容策略
当队列满时(head == tail):
-
计算新容量 = 当前容量 * 2(保持2的幂次)
-
分配新数组
-
将元素从旧数组复制到新数组,保持顺序
-
调整 head 和 tail 指针
-
扩容时元素重新排列,保持 head 在位置0
private void doubleCapacity() {
int n = elements.length;
// head到数组末尾的元素数
int r = n - head;
int newCapacity = n << 1; // 双倍
Object[] a = new Object[newCapacity];
System.arraycopy(elements, head, a, 0, r);
System.arraycopy(elements, 0, a, r, head);
elements = a;
head = 0;
tail = n;
}
4. 双端队列头部插入代码性能解析
public void addFirst(E e) {
if (e == null)
throw new NullPointerException();
elements[head = (head - 1) & (elements.length - 1)] = e;
if (head == tail)
doubleCapacity();
}
1. 索引计算优化
head = (head - 1) & (elements.length - 1) 这行代码实现了:
-
循环数组索引计算:当 head-1 为负时自动"绕回"数组末尾
-
位运算替代取模:要求数组长度必须是 2 的幂次方,这样
length - 1就是一个全1的二进制数-
例如:数组长度 8 (1000),length-1 = 7 (0111)
-
(head - 1) & 7等价于(head - 1) % 8但效率更高
-
2. 时间复杂度
-
最好情况:O(1) - 直接计算新位置并插入
-
最坏情况:O(n) - 触发扩容时需要复制所有元素
-
分摊时间复杂度:仍然是 O(1),因为扩容操作会被分摊到多次插入操作中
性能特点
-
时间复杂度:
-
插入/删除操作:平均 O(1)
-
扩容操作:O(n),但分摊到每次操作仍为 O(1)
-
-
空间效率:比 LinkedList 更节省空间(不需要节点对象开销)
-
线程不安全:非线程安全,需要外部同步
核心方法
1. 队列操作(FIFO)
| 方法 | 说明 | 特殊值处理 | 抛出异常 |
|---|---|---|---|
| offer(e) | 添加到队尾 | 返回 false | 无 |
| poll() | 移除并返回队首 | 返回 null | 无 |
| peek() | 查看队首元素 | 返回 null | 无 |
| add(e) | 添加到队尾 | - | 抛出 IllegalStateException |
| remove() | 移除并返回队首 | - | 抛出 NoSuchElementException |
| element() | 查看队首元素 | - | 抛出 NoSuchElementException |
2. 双端队列操作
| 方法 | 队首 | 队尾 |
|---|---|---|
| 插入 | addFirst(e)/offerFirst(e) | addLast(e)/offerLast(e) |
| 移除 | removeFirst()/pollFirst() | removeLast()/pollLast() |
| 查看 | getFirst()/peekFirst() | getLast()/peekLast() |
3. 栈操作(LIFO)
| 方法 | 等效方法 | 说明 |
|---|---|---|
| push(e) | addFirst(e) | 入栈 |
| pop() | removeFirst() | 出栈 |
| peek() | peekFirst() | 查看栈顶 |
与 LinkedList 比较
| 特性 | ArrayDeque | LinkedList |
|---|---|---|
| 底层结构 | 动态数组 | 双向链表 |
| 内存占用 | 更少 | 更多(节点开销) |
| 随机访问性能 | 更好(数组连续存储) | 较差(需要遍历) |
| 插入删除性能 | 平均 O(1) | 平均 O(1) |
| 适用场景 | 高频队列/栈操作 | 需要频繁在中间插入/删除 |
| 操作类型 | ArrayDeque | LinkedList |
|---|---|---|
| 头部插入 | 分摊 O(1) | O(1) |
| 尾部插入 | 分摊 O(1) | O(1) |
| 头部删除 | 严格 O(1) | O(1) |
| 尾部删除 | 严格 O(1) | O(1) |
| 中间插入 | 不支持(需O(n)) | O(1) |
| 按值删除 | O(n) | O(n) |
| 内存占用 | 更优(连续数组) | 每个元素需节点对象 |
ArrayDeque 在大多数情况下是比 LinkedList 更好的队列/栈实现选择,除非需要频繁在中间位置插入/删除元素。
使用示例
1. 作为队列使用
// 创建队列
Queue<String> queue = new ArrayDeque<>();
// 入队
queue.offer("First");
queue.offer("Second");
// 出队
while(!queue.isEmpty()) {
System.out.println(queue.poll()); // 输出 First, Second
}
2. 作为双端队列使用
Deque<Integer> deque = new ArrayDeque<>();
// 前端添加
deque.addFirst(1); // [1]
deque.offerFirst(2); // [2, 1]
// 后端添加
deque.addLast(3); // [2, 1, 3]
// 前后端操作
System.out.println(deque.removeFirst()); // 2
System.out.println(deque.pollLast()); // 3
3. 作为栈使用
Deque<String> stack = new ArrayDeque<>();
// 压栈
stack.push("Java");
stack.push("Python");
// 弹栈
while(!stack.isEmpty()) {
System.out.println(stack.pop()); // 输出 Python, Java
}
最佳实践
初始容量选择:
// 预估元素数量设置初始容量,避免频繁扩容
Deque<Integer> deque = new ArrayDeque<>(100);
替代 Stack 类:
// 更推荐使用 ArrayDeque 而非 Stack 类
Deque<String> stack = new ArrayDeque<>();
批量操作优化:
// 使用 addAll 批量添加
deque.addAll(Arrays.asList(1, 2, 3));
线程安全方案:
// 需要线程安全时使用包装器
Deque<String> safeDeque = Collections.synchronizedDeque(new ArrayDeque<>());
常见问题
Q1: ArrayDeque 和 LinkedList 如何选择?
-
需要频繁随机访问 → 都不适合,考虑 ArrayList
-
主要用作栈/队列 → ArrayDeque 性能更好
-
需要频繁在中间插入/删除 → LinkedList
Q2: ArrayDeque 允许 null 元素吗?
-
不允许,插入 null 会抛出 NullPointerException
Q3: 为什么作为栈比 Stack 类更好?
-
Stack 类基于 Vector,所有方法同步,性能较差
-
ArrayDeque 专为双端操作优化,无同步开销
ArrayDeque 是 Java 中最通用的线性集合实现之一,合理使用可以显著提升程序性能。在大多数需要栈或队列的场景中,它应该是首选实现。
更多java干货,请查看专栏 Java基础之集合专栏
HashMap详解 HashLink详解 ArrayList详解 CopyOnWriteArrayList详解 HashSet详解 LinkedHashMap详解 LinkedHashSet详解 TreeSet详解 TreeMap详解 ConcurrentHashMap详解 PriorityBlockingQueue 详解 PriorityQueue 详解
774

被折叠的 条评论
为什么被折叠?



