Java集合系列:一文解读ArrayDeque源码「JDK11」
文章目录
一、概述
假设你对ArrayDeque的源码一无所知,那么仅凭ArrayDeque名字来看的话,大概可以猜到它的实现和Array数组有关。
ArrayDeque是Java Collections Framework的一个成员,它的底层是基于定长数组实现的一个双端队列,如果数组存放满了,就会通过扩容机制重新生成一个更大的数组来存放数据,并且维护了双端队列头部元素的索引head和尾部元素的索引tail,使得它成为了一个逻辑上的循环数组,所谓循环是指元素到数组尾之后可以接着从数组头开始。
我们知道,数组非尾部的插入和删除效率是比较低的,然而,这在ArrayDeque上的表现却是效率高,这是怎么实现的呢?
接下来,本文会叙述ArrayDeque是如何维护这样一个循环数组,它的扩容机制是怎么实现的,这些都是ArrayDeque的核心所在;除此之外,本文还会叙述ArrayDeque的基本操作是怎样实现的,以及其它的细节。
好了,让我们一起逐步揭开它的神秘面纱。
二、ArrayDeque源码解读
2.1、继承关系

从UML类图中可以看到,ArrayDeque直接或间接实现了Iterable、Collection、Deque、Queue、Cloneable、Serializable这6个接口;ArrayDeque继承了AbstractCollection这个抽象类。
ArrayDeque和LinkedList在继承关系上是比较相似的,所以针对相同继承关系内容的叙述,比如说Deque接口、Queue接口的解读,本文不再次叙述,想去了解的话,可参考之前的Java集合系列:一文解读LinkedList源码「JDK11」一文。
2.2、成员变量
|
|
从成员变量head、tail的注释可以知道,数组的长度、头部元素、尾部元素都和这两个变量有关,那么当数组初始化后,这样的数组表现形式有4种情况:
- 头部元素的索引head == 尾部元素的索引tail,那么数组为空,也就是数组内部没有元素,如下图所示:

- 头部元素的索引head < 尾部元素的索引tail,那么头部元素为elements[head],尾部元素为elements[tail-1],元素索引从head到tail-1,如下图所示:

- 头部元素的索引head > 尾部元素的索引tail && 尾部元素的索引tail == 0,那么头部元素为elements[head],尾部元素为elements[elements.length-1],元素索引从head到elements.length-1,如下图所示:

- 头部元素的索引head > 尾部元素的索引tail && 尾部元素的索引tail != 0,那么头部元素为elements[head],尾部元素为elements[tail-1],元素索引从head到elements.length-1,然后再从0到tail-1,如下图所示:

2.3、构造方法
|
|
我们看下第二个构造方法,需要传入一个numElements,这里有3种情况确定数组大小:
-
如果numElements < 1,那么数组大小为1;
-
如果numElements == Integer.MAX_VALUE,那么数组大小为Integer.MAX_VALUE;
-
如果numElements >= 1 && numElements < Integer.MAX_VALUE,那么数组大小为numElements + 1;
第三种情况:为什么要numElements + 1呢?
因为循环数组必须时刻至少留一个空位,tail变量指向下一个空位,为了容纳numElements个元素,至少需要numElements+1个位置。
2.4、核心方法
2.4.1、插入
|
|
2.4.2、删除
|
|
2.4.3、检索
|
|
2.4.4、扩容机制
|
|
下面通过一个小例子去演示扩容机制的执行流程,如下代码:
|
|
2.5、其它方法
|
|
三、总结
在ArrayDeque的源码中,并没有与并发相关的代码,所以说ArrayDeque是非线程安全类,在并发环境下多个线程同时操作,会引发不可预知的错误。
ArrayDeque没有索引位置的概念,不能根据索引位置进行操作,所以没有更改操作,如set方法。
另外,ArrayDeque禁止添加空元素。最关键一点是:ArrayDeque用作堆栈时很可能比Stack快,用作队列时比LinkedList快。