JDK容器学习之Queue: ArrayDeque

时间:2022-04-27
本文章向大家介绍JDK容器学习之Queue: ArrayDeque,主要内容包括数组双端队列 ArrayDeque、2. 常见接口实现方式、3. 使用姿势&小结、基本概念、基础应用、原理机制和需要注意的事项等,并结合实例形式分析了其使用技巧,希望通过本文能帮助到大家理解应用这部分内容。

数组双端队列 ArrayDeque

双端队列,表示可以添加元素到(或删除,获取)队列头也可以添加元素到(或删除,获取)队列尾

1. 底层数据结构

类中定义成员变量,一个数组和两个int

transient Object[] elements;

transient int head;

transient int tail;

数据结构比较清晰,就是一个数组,head指向队列的头,tail指向队列的尾

数组定义要求数组的容量为2的n次幂

2. 常见接口实现方式

删除元素

先看删除逻辑,因为比较简单,实现如下

public E poll() {
    if (size == 0) // 队列为空
        return null;
    int s = --size;
    modCount++;
    // 数组中第一个为队列头
    E result = (E) queue[0]; 
    E x = (E) queue[s];
    queue[s] = null;
    if (s != 0) 
        // 队列非空时,重排剩下的元素
        siftDown(0, x);
    return result;
}

添加元素

在队头和队尾添加的逻辑基本一致,这里以在队列尾添加元素绩进行分析

public void addLast(E e) {
    if (e == null) // 不支持向队列中塞入null对象
        throw new NullPointerException();
    elements[tail] = e;
    if ( (tail = (tail + 1) & (elements.length - 1)) == head) {
    // tail 后移一位,若tail长度超过数组长度,则tail转到数组头
    // 上面的与操作等价于对数组长度进行求余
    // 若tail和head相等,则表示数组内容填充满,需要扩容
        doubleCapacity();
    }
}

// 数组扩容方法
private void doubleCapacity() {
    assert head == tail;
    int p = head;
    int n = elements.length;
    int r = n - p; // number of elements to the right of p
    // 新的容量为原来的两倍
    int newCapacity = n << 1;
    if (newCapacity < 0) // int逸出判断
        throw new IllegalStateException("Sorry, deque too big");
    Object[] a = new Object[newCapacity];
    System.arraycopy(elements, p, a, 0, r);
    System.arraycopy(elements, 0, a, r, p);
    elements = a;
    head = 0;
    tail = n;
}

下面是一个数组扩容的逻辑示意图

  • 图中上面的为原数组,添加元素8之后,tail和head相等,都是5
  • 原队列容量为8,顺序为 1 -> 9 -> 5 -> 7 -> 4 -> 3 -> 5 -> 8
  • 扩容后,数组容量为16,顺序保持不变,head为0,tail为8

弹出元素

以弹出队头的元素为例,会直接返回队列头的元素,并将head前移一位,并将数组中源队列头的数据清掉(赋值为null)

public E pollFirst() {
    int h = head;
    @SuppressWarnings("unchecked")
    E result = (E) elements[h];
    // Element is null if deque empty
    if (result == null)
        return null;
    elements[h] = null;     // Must null out slot
    head = (h + 1) & (elements.length - 1);
    return result;
}

删除元素的示意图如下

3. 使用姿势&小结

  1. 若能确定队列的容量,在使用时请指定初始化容量,避免频繁的扩容
  2. 数组的实际容量必须为2的n次幂;初始化时传入一个非2的n次幂的容量参数时,会自动查找到刚好大于该参数的2的n次幂作为数组的实际长度
  3. 队列中不能有空元素
  4. 只有向队列中添加元素超过容量时,才会触发扩容逻辑(扩容为之前的两倍)
  5. 扩容后,数组中的实际顺序和队列顺序一致(即head会指向0,设计到数组的重排)
  6. head指向的是队列中第一个元素的下标位置;tail指向的是队列最后一个元素的后一位索引
  7. 队列头添加元素,是在head前一个数组位置处赋值;在队列尾添加元素是直接在tail指向的数组位置赋值
  8. 队列未发生扩容时,出队和进队都不会导致数组重排,只会改变head或tail的值而已