java集合详解 - ArrayDeque 详解

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):

  1. 计算新容量 = 当前容量 * 2(保持2的幂次)

  2. 分配新数组

  3. 将元素从旧数组复制到新数组,保持顺序

  4. 调整 head 和 tail 指针

  5. 扩容时元素重新排列,保持 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),因为扩容操作会被分摊到多次插入操作中

性能特点

  1. 时间复杂度

    • 插入/删除操作:平均 O(1)

    • 扩容操作:O(n),但分摊到每次操作仍为 O(1)

  2. 空间效率:比 LinkedList 更节省空间(不需要节点对象开销)

  3. 线程不安全:非线程安全,需要外部同步

核心方法

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 比较

特性ArrayDequeLinkedList
底层结构动态数组双向链表
内存占用更少更多(节点开销)
随机访问性能更好(数组连续存储)较差(需要遍历)
插入删除性能平均 O(1)平均 O(1)
适用场景高频队列/栈操作需要频繁在中间插入/删除
操作类型ArrayDequeLinkedList
头部插入分摊 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 详解

评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值