概述
功能:该类为JAVA
数据结构之一,双端队列(Deque)
的数组实现
开始版本:JDK 1.6
注意:
1. 本类为具体的实现类,所有的接口方法和抽象方法均给出了实现
2. 数据结构:底层数据结构为数组(顺序表),因为是双端的,可以作为队列(FIFO)
或堆栈(FILO)
使用
3. 有序、可储存重复元素,不可储存null
值
4. 线程不安全
5. 作为栈使用时比Stack
类效率高,作为队列使用时比LinkedList
快一些
6. 迭代器与大多数集合一样,是快速失败(fail-fast)
的,而非安全失败(fail-safe)
,当集合有结构上的改变时会直接抛出ConcurrentModificationException
异常
7. 扩容机制为扩容为原容量的2
倍(大于原容量的最小2
的幂),例8
-> 16
8. 未指定初始大小时默认容量为16
,指定了初始大小时,若指定的初始大小小于8
则初始容量为8
,指定的初始大小大于8
的情况取大于指定值的最小2的幂
9. 调用addFirst()
和addLast()
后,若数组已满则调用扩容方法
继承类:java.util.AbstractCollection
实现接口:Deque<E>, Cloneable, Serializable
所在包:java.util
导入包:import java.io.Serializable
(序列化)、import java.util.function.Consumer
(函数式接口)、import sun.misc.SharedSecrets
(获取Java栈帧中存储的类信息)
类声明:
public class ArrayDeque<E> extends AbstractCollection<E> implements Deque<E>, Cloneable, Serializable {}
框架图:
属性
常量
私有常量
01.双端队列的最小容量 MIN_INITIAL_CAPACITY
// 双端队列的最小容量(8),注意容量必须是2的幂
private static final int MIN_INITIAL_CAPACITY = 8;
变量
包变量
01.储存双端队列元素 elements
拓展:transient
—— 关键字,不进行序列化,但若实现了Externalizable
,以具体实现决定是否序列化,transient
关键字失效,而Serializable
会受transient
影响,有则不进行序列化
// 储存双端队列元素的数组
transient Object[] elements; // non-private to simplify nested class access
02.头部元素的索引 head
// 双端队列头部元素的索引,若数组为空则此值与tail的值相等(0)
transient int head;
03.尾部元素的索引 tail
// 双端队列尾部元素的索引,若数组为空此值为0
transient int tail;
方法
构造器
01.无参构造器 ArrayDeque()
注意:此处可以看到是用无参构造器创建实例时(未指定容量),默认容量为16
,而不是元素数组的最小容量8
// 源码
public ArrayDeque() {
elements = new Object[16];
}
02.根据指定容量创建实例 ArrayDeque(int numElements)
注意:此处可以看出并非指定多少容量,实例就是多少容量,会根据指定容量计算,若指定容量小于8
则使用8
为容量,否则使用大于等于指定容量最小2的幂
// 源码
public ArrayDeque(int numElements) {
// 根据指定容量计算出最小2的幂,以计算出的值为容量
allocateElements(numElements);
}
03.创建实例并根据指定集合初始化 ArrayDeque(Collection<? extends E> c)
注意:容量根据集合元素个数计算,元素个数小于8
则使用8
为容量,否则使用大于等于元素个数最小2的幂
// 源码
public ArrayDeque(Collection<? extends E> c) {
// 根据指定集合元素个数计算出最小2的幂,以计算出的值为容量
allocateElements(c.size());
// 将集合内元素添加至实例中,此方法后续有详细解析
addAll(c);
}
内部类
01.迭代器(正序) DeqIterator
注意:
1. 本内部类的循环处理方法增加了一些判断(判空、判循环中结构改变导致元素为null
),即本循环方法中若有某种原因(例:删除导致结构变更)导致next()
为null
会抛出ConcurrentModificationException
异常,正常情况下与Iterator
中的默认方法效果一样
2. 直接使用对象的removeFirst()
、addFrist()
等方法会导致结构变更,且迭代器并未进行同步,如需移除请使用迭代器内部的remove()
方法
3. 迭代器中的remove()
方法禁止连续使用,因为连续使用回导致对象中元素混乱
// 源码
private class DeqIterator implements Iterator<E> {
// 当前指针位置(指针在索引前)
private int cursor = head;
// 尾指针位置(指针在索引前)
private int fence = tail;
// 上一个指针位置(结构变更标识),默认-1
private int lastRet = -1;
// 当前指针位置等于尾指针位置,即表示后续没有元素了
public boolean hasNext() {
return cursor != fence;
}
// 获取当前指针后的元素
public E next() {
if (cursor == fence)
throw new NoSuchElementException();
@SuppressWarnings("unchecked")
E result = (E) elements[cursor];
// 当前指针并不等于尾指针(当前指针后应当有元素),但是元素为null(可能结构变更了)
if (tail != fence || result == null)
throw new ConcurrentModificationException();
// 设置当前指针为上一个指针位置
lastRet = cursor;
// 指针后移
cursor = (cursor + 1) & (elements.length - 1);
return result;
}
// 移除当前指针后的元素
public void remove() {
// 在本方法前已进行了结构操作并未进行next()操作,lastRet为-1,禁止连续调用,防止元素混乱
if (lastRet < 0)
throw new IllegalStateException();
// 若距离头指针较近,结构移动的是指针前的元素不需要修改当前指针位置,本操作后会调用next(),也不用担心读取到移动过来的元素
if (delete(lastRet)) {
cursor = (cursor - 1) & (elements.length - 1);
fence = tail;
}
// 重置前一个指针位置,防止连续修改结构
lastRet = -1;
}
// 根据传入的函数式接口,循环处理当前指针后的元素
public void forEachRemaining(Consumer<? super Integer> action) {
Objects.requireNonNull(action);
Object[] a = elements;
// 不直接使用类变量,是因为类变量可能会被其他操作改变
int m = a.length - 1, f = fence, i = cursor;
// 将当前指针设置为尾指针
cursor = f;
// 循环至缓存的当前指针等于尾指针
while (i != f) {
@SuppressWarnings("unchecked") Integer e = (Integer)a[i];
i = (i + 1) & m;
if (e == null)
throw new ConcurrentModificationException();
// 处理元素
action.accept(e);
}
}
}
// 循环方法 forEachRemaining 示例
@Test
void contextLoads(){
Integer[] elements = new Integer[]{7,1,1,2,3,4,5,6};
ArrayDeque<Integer> arrayDeque = new ArrayDeque<>(Arrays.asList(elements));
Iterator<Integer> iterator = arrayDeque.iterator();
iterator.next();
// 1 1 2 3 4 5 6
iterator.forEachRemaining(l -> System.out.print(l + " "));
}
// 错误示例:生成迭代器后调用对象的removeFirst()方法
@Test
void contextLoads() {
Integer[] elements = new Integer[]{7,1,1,2,3,4,5,6};
ArrayDeque<Integer> arrayDeque = new ArrayDeque<>(Arrays.asList(elements));
Iterator<Integer> iterator = arrayDeque.iterator();
arrayDeque.removeFirst();
// java.util.ConcurrentModificationException
iterator.next();
}
// 错误示例:连续调用迭代器的remove()方法
@Test
void contextLoads() {
Integer[] elements = new Integer[]{7,1,1,2,3,4,5,6};
ArrayDeque<Integer> arrayDeque = new ArrayDeque<>(Arrays.asList(elements));
Iterator<Integer> iterator = arrayDeque.iterator();
iterator.next();
iterator.remove();
// java.lang.IllegalStateException
iterator.remove();
}
02.迭代器(逆序) DescendingIterator
注意:
1. 本方法未重写循环处理元素的方法
2. 本迭代器取值时取的是指针前的值
而不是指针后,但是根据下标获取值时获取的就是指定下标的值,因此调用next()
需要先将指针前移再获取下标的值
3. 直接使用对象的removeFirst()
、addFrist()
等方法会导致结构变更,且迭代器并未进行同步,如需移除请使用迭代器内部的remove()
方法
4. 迭代器中的remove()
方法禁止连续使用,因为连续使用回导致对象中元素混乱
// 源码
private class DescendingIterator implements Iterator<E> {
// 当前指针位置(指针在索引前),逆序使用尾索引
private int cursor = tail;
// 尾指针位置(指针在索引前),逆序使用头索引
private int fence = head;
// 上一个指针位置(结构变更标识),默认-1
private int lastRet = -1;
// 当前指针位置等于尾指针位置即表示后续没有元素了
public boolean hasNext() {
return cursor != fence;
}
// 获取当前指针后的元素
public Integer next() {
// 当前指针位置等于尾指针位置(当前指针后无元素)
if (cursor == fence)
throw new NoSuchElementException();
// 指针前移(根据指针获取元素均是获取指针后的元素,因此需要先前移,这样获取到的元素才是原当前指针前的数据)
cursor = (cursor - 1) & (elements.length - 1);
@SuppressWarnings("unchecked")
Integer result = (Integer) elements[cursor];
// 当前指针并不等于尾指针(当前指针后应当有元素),但是元素为null(可能结构变更了)
if (head != fence || result == null)
throw new ConcurrentModificationException();
// 设置当前指针为上一个指针位置
lastRet = cursor;
return result;
}
// 移除当前指针后的元素
public void remove() {
// 在本方法前已进行了结构操作并未进行next()操作,当前指针后已无元素
if (lastRet < 0)
throw new IllegalStateException();
// 若距离尾指针较近,结构移动的是指针后的元素不需要修改当前指针位置(本迭代器获取的是指针前的元素)
// 本操作后会调用next(),也不用担心读取到移动过来的元素
if (!delete(lastRet)) {
cursor = (cursor + 1) & (elements.length - 1);
fence = head;
}
// 重置前一个指针位置,防止连续修改结构
lastRet = -1;
}
}
// 错误示例:生成迭代器后调用对象的`removeFirst()`方法
@Test
void contextLoads() {
Integer[] elements = new Integer[]{7,1,1,2,3,4,5,6};
ArrayDeque<Integer> arrayDeque = new ArrayDeque<>(Arrays.asList(elements));
// descendingIterator() 生成的是反向迭代器
Iterator<Integer> iterator = arrayDeque.descendingIterator();
arrayDeque.removeFirst();
// java.util.ConcurrentModificationException
iterator.next();
}
// 错误示例:连续调用迭代器的remove()方法
@Test
void contextLoads() {
Integer[] elements = new Integer[]{7,1,1,2,3,4,5,6};
ArrayDeque<Integer> arrayDeque = new ArrayDeque<>(Arrays.asList(elements));
// descendingIterator() 生成的是反向迭代器
Iterator<Integer> iterator = arrayDeque.descendingIterator();
iterator.next();
iterator.remove();
// java.lang.IllegalStateException
iterator.remove();
}
03.可拆分迭代器DeqSpliterator<E>
注意:
1. 本方法主要用于并行时使用迭代器,可以提高效率,但并非一定会拆分,有些情况会拆分失败
2. 拆分规则 —— 若已经开始遍历或元素太少会拆分失败,若可以拆分则每次拆分一半,将前面一半
的元素拆分成一个新的可拆分迭代器,奇数个元素拆分出去的个数是较少的部分(例:元素一共有7个,则拆分出去的是前3个元素)
3. 可拆分迭代器全部的特性 —— ORDERED(定义排序)、DISTINCT(唯一)、IMMUTABLE(不可变)、NONNULL(不可为NULL)、SIZED(有固定大小)、SORTED(有序)、CONCURRENT(可并发修改)、SUBSIZED(拆分后子大小不变)
4. 本可拆分迭代器特性 —— ORDERED(定义排序)、NONNULL(不可为NULL)、SIZED(有固定大小)、SUBSIZED(拆分后子大小不变)
5. 本方法为JDK 1.8
新增方法,支持JDK 1.8
及以后版本使用
// 源码
static final class DeqSpliterator<E> implements Spliterator<E> {
// 双端队列对象
private final ArrayDeque<E> deq;
// 尾部索引,默认-1
private int fence;
// 当前索引,遍历和分割的时候会修改
private int index;
// 构造器
DeqSpliterator(ArrayDeque<E> deq, int origin, int fence) {
this.deq = deq;
this.index = origin;
this.fence = fence;
}
private int getFence() {
int t;
// 若fence小于0对参数进行初始化,给fence赋值队列的尾索引,给index赋值队列的头索引
if ((t = fence) < 0) {
t = fence = deq.tail;
index = deq.head;
}
return t;
}
// 拆分迭代器,并不一定成功
public DeqSpliterator<E> trySplit() {
int t = getFence(), h = index, n = deq.elements.length;
// 判断是否有元素,且元素数量不是很少才进行拆分
if (h != t && ((h + 1) & (n - 1)) != t) {
if (h > t)
// 为了便于计算一般的个数,先将循环队列头索引前面的值暂时扩展到后面
// (只获取计算后的值,并非真的扩容移动元素)
t += n;
// 计算头索引后一半元素处的索引
int m = ((h + t) >>> 1) & (n - 1);
return new DeqSpliterator<>(deq, h, index = m);
}
return null;
}
// 根据传入的函数式接口,循环处理当前索引及之后的元素,实现与上述的普通迭代器基本一致
public void forEachRemaining(Consumer<? super E> consumer) {
if (consumer == null)
throw new NullPointerException();
Object[] a = deq.elements;
int m = a.length - 1, f = getFence(), i = index;
index = f;
while (i != f) {
@SuppressWarnings("unchecked") E e = (E)a[i];
i = (i + 1) & m;
if (e == null)
throw new ConcurrentModificationException();
consumer.accept(e);
}
}
// 根据传入的函数式接口,处理当前索引的元素,本方法会后移索引
public boolean tryAdvance(Consumer<? super E> consumer) {
if (consumer == null)
throw new NullPointerException();
Object[] a = deq.elements;
int m = a.length - 1, f = getFence(), i = index;
// 判断是否已到尾索引,是则返回false
if (i != fence) {
@SuppressWarnings("unchecked") E e = (E)a[i];
index = (i + 1) & m;
// 若元素为null,抛出 ConcurrentModificationException 异常
if (e == null)
throw new ConcurrentModificationException();
consumer.accept(e);
return true;
}
return false;
}
// 当前队列还剩多少元素
public long estimateSize() {
// 头尾索引直接距离 = 尾索引 - 当前索引
int n = getFence() - index;
if (n < 0)
// 小于0则表示是循环数组,需要加上数组最大索引计算个数,否则直接返回即可
n += deq.elements.length;
return (long) n;
}
// 迭代器拥有的特性,此处返回的是特性或运算后的结果
// ORDERED(定义排序)、NONNULL(不可为NULL)、SIZED(有固定大小)、SUBSIZED(拆分后子大小不变)
@Override
public int characteristics() {
return Spliterator.ORDERED | Spliterator.SIZED |
Spliterator.NONNULL | Spliterator.SUBSIZED;
}
}
// 示例
@Test
void contextLoads() {
Integer[] elements = new Integer[]{0,1,2,3,4,5,6};
ArrayDeque<Integer> arrayDeque = new ArrayDeque<>(Arrays.asList(elements));
arrayDeque.removeFirst();
arrayDeque.add(7);
arrayDeque.removeFirst();
arrayDeque.add(8);
// [8, null, 2, 3, 4, 5, 6, 7]
Spliterator<Integer> spliterator = arrayDeque.spliterator();
// 16720
System.out.println(spliterator.characteristics());
// 剩余元素个数:7
System.out.println(spliterator.estimateSize());
// 2
spliterator.tryAdvance(System.out::println);
// 3
spliterator.tryAdvance(System.out::println);
// 剩余元素个数:5
System.out.println(spliterator.estimateSize());
// 4 5 6 7 8
spliterator.forEachRemaining(l -> System.out.print(l + " "));
}
// 拆分示例
@Test
void contextLoads() {
Integer[] elements = new Integer[]{0,1,2,3,4,5,6};
ArrayDeque<Integer> arrayDeque = new ArrayDeque<>(Arrays.asList(elements));
arrayDeque.removeFirst();
arrayDeque.add(7);
arrayDeque.removeFirst();
arrayDeque.add(8);
// [8, null, 2, 3, 4, 5, 6, 7]
Spliterator<Integer> spliterator = arrayDeque.spliterator();
Spliterator<Integer> trySplit = spliterator.trySplit();
// 5 6 7 8
spliterator.forEachRemaining(l -> System.out.print(l + " "));
System.out.println();
// 2 3 4
trySplit.forEachRemaining(l -> System.out.print(l + " "));
}
私有方法
01.计算当前双端队列所需容量 calculateSize(int numElements)
参数:numElements
—— 指定的容量
注意:
1. 双端队列储存元素的数组默认容量为8
,但双端队列未指定容量时的初始默认容量并不是8
,而是16
2. 本方法主要是为了保证容量为2
的幂,若指定容量小于
初始容量则返回初始容量8
,否则计算出大于等于指定容量的最小2
的幂
3. 因为最小容量为8
,即0100
,所以右移操作有效的右移位数最小为1,即右移1位,若数值在9~16
之间,实际进行initialCapacity |= (initialCapacity >>> 1)
就可以得到最小2的幂,而17~64
就必须执行initialCapacity |= (initialCapacity >>> 2)
才可以得到最小2的幂,以此类推
4. 此处最大右移位数为16
是因为initialCapacity
是int
型的,最大为 231-1,而进行到最后一步initialCapacity |= (initialCapacity >>> 16)
已右移了31
位,无需再次右移了
// 源码
private static int calculateSize(int numElements) {
// MIN_INITIAL_CAPACITY = 8
int initialCapacity = MIN_INITIAL_CAPACITY;
// 大于等于8时进行右移操作
if (numElements >= initialCapacity) {
initialCapacity = numElements;
// 通过右移来计算最小2的幂,指定数值越小有效右移数越小
initialCapacity |= (initialCapacity >>> 1);
initialCapacity |= (initialCapacity >>> 2);
initialCapacity |= (initialCapacity >>> 4);
initialCapacity |= (initialCapacity >>> 8);
initialCapacity |= (initialCapacity >>> 16);
// 以上右移方法会将值计算为末尾均为1的值,需要使用++加一使其变为最小2的幂
initialCapacity++;
// 若右移后溢出了(数值小于0)则将其右移一位,容量变为2^30
if (initialCapacity < 0)
initialCapacity >>>= 1;
}
return initialCapacity;
}
// 解析示例
@Test
void contextLoads() {
int initialCapacity = 8;
int numElements = 159;
if (numElements >= initialCapacity) {
initialCapacity = numElements;
initialCapacity |= (initialCapacity >>> 1);
initialCapacity |= (initialCapacity >>> 2);
initialCapacity |= (initialCapacity >>> 4);
initialCapacity |= (initialCapacity >>> 8);
initialCapacity |= (initialCapacity >>> 16);
initialCapacity++;
if (initialCapacity < 0)
initialCapacity >>>= 1;
}
// 256
System.out.println(initialCapacity);
}
02.初始化元素数组 allocateElements(int numElements)
参数:numElements
—— 指定的容量
注意:
1. elements
是用于储存双端队列元素的数组
2. 本方法主要用于指定了初始化内容调用构造器时创建数组对象
// 源码
private void allocateElements(int numElements) {
elements = new Object[calculateSize(numElements)];
}
02.初始化元素数组 allocateElements(int numElements)
参数:numElements
—— 指定的容量
注意:
1. elements
是用于储存双端队列元素的数组
2. 本方法主要用于指定了初始化内容调用构造器时创建数组对象
// 源码
private void allocateElements(int numElements) {
elements = new Object[calculateSize(numElements)];
}
03.数组扩容 doubleCapacity()
拓展:assert
—— 关键字(断言),用于判断后面的表达式结果是否为true
,不是则抛出AssertionError
异常
注意:
1. 双端队列扩容机制为扩容为大于原容量的最小2的幂
2. 本方法仅当数组满了
才会调用
3. 双端队列尾索引有可能小于头索引
,当头索引不等于0,而最大索引值已使用且头索引前未使用,尾索引会从0索引开始依次后移 (例:数组[2,null,5,6,9,10,6,9],头索引为2,则此时尾索引为1,读取出数组为[5,6,9,10,6,9,2,null])
局部变量说明:
1. p
—— 头索引
2. n
—— 数组原容量
3. r
—— 包括头索引位置其后可存放元素个数,例如头索引1,容量8,则可存放元素个数为7
4. newCapacity
—— 扩容后的容量(左移一位,例8
->16
)
// 源码
private void doubleCapacity() {
// 判断数组是否已满(头尾索引一致则表示数组为空或数组已满)
assert head == tail;
int p = head;
int n = elements.length;
int r = n - p;
int newCapacity = n << 1;
// 判断扩容后的容量是否溢出(超过int型最大值),溢出则抛出IllegalStateException异常
if (newCapacity < 0)
throw new IllegalStateException("Sorry, deque too big");
// 创建新数组,因为此处储存数组元素的是非扩容型数组,所以需要新创建一个新的数组
Object[] a = new Object[newCapacity];
// 拷贝原数组元素至新数组,由于ArrayDeque为双端队列,头索引并不一定为0
// 所以要先将头索引后的元素拷贝至新数组,再将头索引之前的元素拷贝至新数组
System.arraycopy(elements, p, a, 0, r);
System.arraycopy(elements, 0, a, r, p);
// 重置双端队列参数(头索引、尾索引、储存元素的数组)
elements = a;
// 因为上面拷贝元素时先拷贝了头索引之后的元素,因此新数组头索引为0
head = 0;
// 尾索引为原数组的长度(即原数组头索引后最后一个元素的索引 + 1)
tail = n;
}
// 解析示例
@Test
void contextLoads() {
Object[] elements = new Object[]{1,2,3,4,8,17,16,95};
// 头索引为1,数组满时尾索引也会变为1
int head = 1;
int tail = 1;
// 源码逻辑
assert head == tail;
// 头索引 1
int p = head;
// 原数组容量 8
int n = elements.length;
// 头索引后元素个数(包括头索引) 7
int r = n - p;
// 扩容后容量 16
int newCapacity = n << 1;
if (newCapacity < 0)
throw new IllegalStateException("Sorry, deque too big");
// 生成容量为16的数组
Object[] a = new Object[newCapacity];
// 拷贝头索引后的元素(包括头索引)
System.arraycopy(elements, p, a, 0, r);
// 打印:[2, 3, 4, 8, 17, 16, 95, null, null, null, null, null, null, null, null, null]
System.out.println(Arrays.toString(a));
// 拷贝头索引前的元素
System.arraycopy(elements, 0, a, r, p);
// 打印:[2, 3, 4, 8, 17, 16, 95, 1, null, null, null, null, null, null, null, null]
System.out.println(Arrays.toString(a));
elements = a;
// 设置头索引为0
head = 0;
// 设置尾索引为8
tail = n;
}
04.拷贝元素到指定数组 copyElements(T[] a)
参数:a
—— 用于接收拷贝出元素的数组
注意:
1. 本方法未判断指定数组大小是否可以存放全部当前队列元素,在使用时需要先判断,否则会抛出ArrayIndexOutOfBoundsException
异常
2. 若指定的元素内有元素,从0下标开始拷贝元素,会将之前的元素覆盖,但不会清空原数组,本方法也没有将分隔出置0,因此不建议使用已有元素的数组接收拷贝元素
// 源码
private <T> T[] copyElements(T[] a) {
// 根据头索引前是否有元素,决定直接复制还是分成两部分复制,拷贝后的数组为普通数组,没有头尾索引
if (head < tail) {
System.arraycopy(elements, head, a, 0, size());
} else if (head > tail) {
int headPortionLen = elements.length - head;
System.arraycopy(elements, head, a, 0, headPortionLen);
System.arraycopy(elements, 0, a, headPortionLen, tail);
}
return a;
}
// 解析示例1
@Test
void contextLoads6() {
Object[] elements = new Object[]{1,2,3,4,8,17,16,95,15,11};
Object[] a = new Object[16];
int head = 0;
int tail = 9;
int size = elements.length;
// 源码逻辑
if (head < tail) {
// 头索引小于尾索引,正常的数组顺序,直接拷贝有元素的部分即可
System.arraycopy(elements, head, a, 0,size);
} else if (head > tail) {
// 尾索引小于头索引,有部分元素在前面,需要查分开拷贝
int headPortionLen = elements.length - head;
// 先拷贝头索引之后的元素(包括头索引)
System.arraycopy(elements, head, a, 0, headPortionLen);
// 再拷贝头索引之前的元素
System.arraycopy(elements, 0, a, headPortionLen, tail);
}
// [1, 2, 3, 4, 8, 17, 16, 95, 15, 11, null, null, null, null, null, null]
System.out.println(Arrays.toString(a));
}
// 解析示例2:指定数组有元素
@Test
void contextLoads() {
Object[] elements = new Object[]{1,2,3,4,8,17,16,95,15,11};
Object[] a = new Object[]{1,1,1,1,1,1,1,1,1,1,1,1,1,1,1,1,1,1,1};
int head = 0;
int tail = 9;
int size = elements.length;
// 源码逻辑
if (head < tail) {
// 头索引小于尾索引,正常的数组顺序,直接拷贝有元素的部分即可
System.arraycopy(elements, head, a, 0,size);
} else if (head > tail) {
// 尾索引小于头索引,有部分元素在前面,需要查分开拷贝
int headPortionLen = elements.length - head;
// 先拷贝头索引之后的元素(包括头索引)
System.arraycopy(elements, head, a, 0, headPortionLen);
// 再拷贝头索引之前的元素
System.arraycopy(elements, 0, a, headPortionLen, tail);
}
// [1, 2, 3, 4, 8, 17, 16, 95, 15, 11, 1, 1, 1, 1, 1, 1, 1, 1, 1]
System.out.println(Arrays.toString(a));
}
05.数组校验 checkInvariants()
注意:
1. 本方法用于判断队列是否可以移除指定元素,移除指定元素操作会改变结构,导致元素移动因此需要加判断
2. 本方法主要针对两种情况判断,一个是数组不能已满
,一个是数组结构必须合理
// 源码
private void checkInvariants() {
// 判断1:判断尾索引处是否有元素,有元素抛出异常(此时数组可能已满或异常)
assert elements[tail] == null;
// 判断2:头索引等于尾索引时再次判断了一遍数组是否已满(头索引不为null表示尾索引也不为null)
// 判断3:数组非空未满,若尾索引前一位索引的元素为空则抛出异常(出现了未知错误导致尾索引前一位元素无值)
assert head == tail ? elements[head] == null :
(elements[head] != null && elements[(tail - 1) & (elements.length - 1)] != null);
// 判断4:数组非空未满,若头索引前一位索引的元素有值则抛出异常
// (出现了未知错误导致数组未满,尾索引未达到最后一位,头索引前一位却有值)
assert elements[(head - 1) & (elements.length - 1)] == null;
}
// 异常示例1:数组已满
@Test
void contextLoads() {
Object[] elements = new Object[]{1,2,3,4,8,17,16,95,15,11};
int head = 0;
int tail = 0;
int size = elements.length;
// 数组已满 java.lang.AssertionError
assert elements[tail] == null;
assert head == tail ? elements[head] == null :
(elements[head] != null &&
elements[(tail - 1) & (elements.length - 1)] != null);
assert elements[(head - 1) & (elements.length - 1)] == null;
}
// 异常示例2:数组异常,尾索引后面有位置却没有后移
@Test
void contextLoads() {
Object[] elements = new Object[]{1,2,3,4,8,17,16,95,15,null};
int head = 0;
int tail = 8;
int size = elements.length;
// 数组异常,尾索引后面有位置却没有后移 java.lang.AssertionError
assert elements[tail] == null;
assert head == tail ? elements[head] == null :
(elements[head] != null &&
elements[(tail - 1) & (elements.length - 1)] != null);
assert elements[(head - 1) & (elements.length - 1)] == null;
}
// 异常示例3:数组非空未满,尾索引前一位索引的元素为空
@Test
void contextLoads() {
Object[] elements = new Object[]{1,2,3,4,8,17,16,95,null,null};
int head = 0;
int tail = 9;
int size = elements.length;
assert elements[tail] == null;
// 头尾索引不一致,尾索引前一位索引的元素为空
// 此处 [(tail - 1) & (elements.length - 1)] = 8
// java.lang.AssertionError
assert head == tail ? elements[head] == null :
(elements[head] != null && elements[(tail - 1) & (elements.length - 1)] != null);
assert elements[(head - 1) & (elements.length - 1)] == null;
}
// 异常示例4:数组非空未满,尾索引未到达最后一个位置(头索引前),但最后一个位置已有值
@Test
void contextLoads() {
Object[] elements = new Object[]{1,2,3,4,8,17,16,95,11,null};
int head = 1;
int tail = 9;
int size = elements.length;
assert elements[tail] == null;
assert head == tail ? elements[head] == null :
(elements[head] != null && elements[(tail - 1) & (elements.length - 1)] != null);
System.out.println((head - 1) & (elements.length - 1));
// 尾索引未到达最后一个位置(头索引前),但最后一个位置已有值
// 此处 [(head - 1) & (elements.length - 1)] = 0
// java.lang.AssertionError
assert elements[(head - 1) & (elements.length - 1)] == null;
}
06.删除指定元素 delete(int i)
参数:i
—— 要删除的元素的索引值
注意:
1. 本方法会改变结构导致元素移动
2. 队列已满且未扩容的情况下不能删除指定元素
3. 对于调用本方法的removeFirstOccurrence(Object o)
和removeLastOccurrence(Object o)
,本方法中返回的值并未使用,上述两个方法没有进行返回值的判断处理
4. 本方法的返回值不能作为是否删除成功的标识
,其只标识移动的是头索引(返回false)还是尾索引(返回true)
备注:
1. 本详解中的正常顺序包括整个数组就是正常顺序
和数组为循环数组
但是指定下标与头索引/尾索引之间的关系是正常顺序
两种情况
2. 本详解中的循环顺序仅表示指定下标与头索引/尾索引之间的关系是非正常(循环)顺序
// 源码
private boolean delete(int i) {
// 数组校验,数组不能已满,结构必须合理
checkInvariants();
final Object[] elements = this.elements;
// 数组最后一位元素下标(此处与头索引和尾索引无关)
final int mask = elements.length - 1;
final int h = head;
final int t = tail;
// 需移除的索引到达头索引的距离
final int front = (i - h) & mask;
// 需移除的索引到达尾索引的距离
final int back = (t - i) & mask;
// 此处计算了尾索引到头索引的距离,与指定索引到头索引的距离进行比较,以下两种情况会抛异常
// 情况1:不变性检测后,增加元素后队列满了这种情况(距离为0)
// 情况2:不变性检测后,移除元素导致指定索引到头索引的距离小于尾索引到头索引的距离(指定元素可能已被删除)
if (front >= ((t - h) & mask))
throw new ConcurrentModificationException();
// 判断索引元素距头索引远还是距尾索引远,选择元素较少的方向移动元素和头尾索引
// 距头索引较近,变更头索引,移除成功返回false
if (front < back) {
// 头索引小于等于指定索引(正常的数组顺序,从前往后)
if (h <= i) {
// 复制指定索引前面的元素全部后移一位(复制到后面)
System.arraycopy(elements, h, elements, h + 1, front);
} else {
// 头索引大于指定索引(非正常的数组顺序,循环型数组循环部分)
// 将索引[0~指定索引元素前]的元素后移一位(复制到后面)
System.arraycopy(elements, 0, elements, 1, i);
// 将数组最大索引值复制至索引0
elements[0] = elements[mask];
// 将头索引后的元素全部后移一位(正常顺序)
System.arraycopy(elements, h, elements, h + 1, mask - h);
}
// 原头索引设置为null,此索引已不再是头索引了
elements[h] = null;
// 重新计算头索引并赋值(原头索引后一位)
head = (h + 1) & mask;
return false;
} else {
// 距尾索引较近,变更尾索引,移除成功返回true
// 指定索引小于尾索引(正常的数组顺序,从前到后)
if (i < t) {
// 直接将指定索引后尾索引之前的元素前移一位(复制到前面)
System.arraycopy(elements, i + 1, elements, i, back);
// 尾索引前移一位
tail = t - 1;
} else {
// 指定索引大于等于尾索引(非正常的数组顺序,循环型数组循环部分)
// 将指定索引后的元素前移一位
System.arraycopy(elements, i + 1, elements, i, mask - i);
// 将索引0复制至数组最大索引位
elements[mask] = elements[0];
// 将索引[1~尾索引前]元素前移一位
System.arraycopy(elements, 1, elements, 0, t);
// 重新设置尾索引并赋值(原尾索引前一位)
tail = (t - 1) & mask;
}
return true;
}
}
// 测试源码调用方法:后续示例测试时调用本方法,原方法为私有方法
Map<String,Object> testDelete(Object[] elements, int head, int tail, int i) {
Boolean result = null;
final int mask = elements.length - 1;
final int h = head;
final int t = tail;
final int front = (i - h) & mask;
final int back = (t - i) & mask;
if (front >= ((t - h) & mask))
throw new ConcurrentModificationException();
if (front < back) {
if (h <= i) {
System.arraycopy(elements, h, elements, h + 1, front);
} else {
System.arraycopy(elements, 0, elements, 1, i);
elements[0] = elements[mask];
System.arraycopy(elements, h, elements, h + 1, mask - h);
}
elements[h] = null;
head = (h + 1) & mask;
// 原 return false
result = false;
} else {
if (i < t) {
System.arraycopy(elements, i + 1, elements, i, back);
tail = t - 1;
} else {
System.arraycopy(elements, i + 1, elements, i, mask - i);
elements[mask] = elements[0];
System.arraycopy(elements, 1, elements, 0, t);
tail = (t - 1) & mask;
}
// 原 return true
result = true;
}
// 测试结果返回
Map<String,Object> map = new HashMap<>(4);
map.put("result",result);
map.put("head",head);
map.put("tail",tail);
map.put("elements",Arrays.toString(elements));
return map;
}
// 示例1:距头索引近,正常顺序
@Test
void contextLoads() {
// 模拟数据
final Object[] elements = new Object[]{1, null, 9, 2, 8, 4, 8, 18};
int head = 2;
int tail = 1;
// 距头索引近,正常顺序
int i = 2;
// 调用测试方法
Map<String, Object> map = testDelete(elements, head, tail, i);
// {result=false, head=3, tail=1, elements=[1, null, null, 2, 8, 4, 8, 18]}
System.out.println(map);
}
// 示例2:距尾索引近,循环顺序循环部分
@Test
void contextLoads(){
// 模拟数据
final Object[] elements = new Object[]{1, null, 9, 2, 8, 4, 8, 18};
int head = 2;
int tail = 1;
// 距尾索引近,循环顺序
int i = 0;
// 调用测试方法
Map<String, Object> map = testDelete(elements, head, tail, i);
// {result=true, head=2, tail=0, elements=[null, null, 9, 2, 8, 4, 8, 18]}
System.out.println(map);
}
// 示例3:距头索引近,循环顺序循环部分
@Test
void contextLoads() {
// 模拟数据
final Object[] elements = new Object[]{9, 2, 8, 4, 1, null, 8, 18};
int head = 6;
int tail = 5;
// 距头索引近,循环顺序
int i = 0;
// 调用测试方法
Map<String, Object> map = testDelete(elements, head, tail, i);
// {result=false, head=7, tail=5, elements=[18, 2, 8, 4, 1, null, null, 8]}
System.out.println(map);
}
// 示例4:距尾索引近,正常顺序
@Test
void contextLoads() {
// 模拟数据
final Object[] elements = new Object[]{9, 2, 8, 4, 1, null, 8, 18};
int head = 6;
int tail = 5;
// 距尾索引近,正常顺序
int i = 3;
// 调用测试方法
Map<String, Object> map = testDelete(elements, head, tail, i);
// {result=true, head=6, tail=4, elements=[9, 2, 8, 1, null, null, 8, 18]}
System.out.println(map);
}
公有方法
01.新增元素至首位(无返回值) addFirst(E e)
参数:e
—— 新增的元素
注意:
1. 借助该方法可以将此对象看作堆栈(FIFO),数组遍历是从头到尾的,每次将新元素新增至首位即可实现先进先出(堆栈特点)
2. 该方法等价于堆栈的push(E e)
方法
3. 本方法并不是在头索引处依次后移再插入实现的,而是直接在头索引前一个位置(头索引即则是最后一个元素位置)处储存元素,再重新设置头索引实现的,效率比一般的数组高
// 源码
public void addFirst(E e) {
if (e == null)
throw new NullPointerException();
elements[head = (head - 1) & (elements.length - 1)] = e;
// 若新增元素后容量满了则扩容
if (head == tail)
doubleCapacity();
}
// 解析示例
@Test
void contextLoads() {
Object[] elements = new Object[]{1,2,3,4,8,null,null,null};
Integer e = 16;
int head = 0;
int tail = 5;
// 核心源码
// head = (0 - 1) & (8 - 1) -> head = 7
// 此处未标注符号位(-1为已取反+1后的值) 1111 & 0111 = 0111
elements[head = (head - 1) & (elements.length - 1)] = e;
// 此处不是ArrayDeque,而是模拟的内部数组,因此输出顺序不同与ArrayDeque对象 [1,2,3,4,8,null,null,16]
System.out.println(Arrays.toString(elements));
}
// 方法示例
@Test
void contextLoads() {
// 与解析示例相同的初始元素,注意模拟初始元素不能有null,否则addAll会报错
Object[] elements = new Object[]{1,2,3,4,8};
ArrayDeque arrayDeque = new ArrayDeque();
arrayDeque.addAll(Arrays.asList(elements));
arrayDeque.addFirst(16);
// ArrayDeque未获取为null的元素,且根据头索引和尾索引使其有序
// [16, 1, 2, 3, 4, 8]
System.out.println(arrayDeque);
}
02.新增元素至末尾(无返回值) addLast(E e)
参数:e
—— 新增的元素
注意:
1. 借助该方法可以将此对象看作队列(FILO),数组遍历是从头到尾的,每次将新元素新增至末尾即可实现先进后出(队列特点)
2. 该方法等价于堆栈的add(E e)
3. 需注意队列不能储存null
值
// 源码
public void addLast(E e) {
if (e == null)
throw new NullPointerException();
elements[tail] = e;
if ( (tail = (tail + 1) & (elements.length - 1)) == head)
doubleCapacity();
}
// 解析示例
@Test
void contextLoads() {
Object[] elements = new Object[]{null,null,null,1,2,5,6,null};
Integer e = 16;
int head = 3;
int tail = 7;
// 核心源码
elements[tail] = e;
// tail = (7 + 1) & (8 - 1) -> tail = 0
// 1000 & 0111 = 0000
tail = (tail + 1) & (elements.length - 1);
// 此处不是ArrayDeque因此输出顺序不同 [null, null, null, 1, 2, 5, 6, 16]
System.out.println(Arrays.toString(elements));
}
// 方法示例
@Test
void contextLoads() {
Object[] elements = new Object[]{1,2,5,6};
ArrayDeque arrayDeque = new ArrayDeque();
arrayDeque.addAll(Arrays.asList(elements));
arrayDeque.addLast(16);
// [1, 2, 5, 6, 16]
System.out.println(arrayDeque);
}
03.新增元素至首位(有返回值) offerFirst(E e)
参数:e
—— 新增的元素
注意:本方法与addFirst(E e)
唯一的区别就是有返回值,便于判断
// 源码
public boolean offerFirst(E e) {
addFirst(e);
return true;
}
// 方法示例
@Test
void contextLoads() {
Object[] elements = new Object[]{1,2,3,4,8};
ArrayDeque arrayDeque1 = new ArrayDeque();
arrayDeque1.addAll(Arrays.asList(elements));
// 无返回值
arrayDeque1.addFirst(16);
// [16, 1, 2, 3, 4, 8]
System.out.println(arrayDeque1);
ArrayDeque arrayDeque2 = new ArrayDeque();
arrayDeque2.addAll(Arrays.asList(elements));
// 有返回值 true
System.out.println(arrayDeque2.offerFirst(16));
// [16, 1, 2, 3, 4, 8]
System.out.println(arrayDeque2);
}
04.新增元素至末尾(有返回值) offerLast(E e)
参数:e
—— 新增的元素
注意:
1. 本方法与addLast(E e)
唯一的区别就是有返回值,便于判断
2. 本方法等效于队列的offer()
方法
// 源码
public boolean offerLast(E e) {
addLast(e);
return true;
}
// 示例
@Test
void contextLoads() {
Object[] elements = new Object[]{1,2,3,4,8};
ArrayDeque arrayDeque1 = new ArrayDeque();
arrayDeque1.addAll(Arrays.asList(elements));
// 无返回值
arrayDeque1.addLast(16);
// [1, 2, 3, 4, 8, 16]
System.out.println(arrayDeque1);
ArrayDeque arrayDeque2 = new ArrayDeque();
arrayDeque2.addAll(Arrays.asList(elements));
// 有返回值 true
System.out.println(arrayDeque2.offerLast(16));
// [1, 2, 3, 4, 8, 16]
System.out.println(arrayDeque2);
}
05.移除首位元素(判空) removeFirst()
注意:
1. 本方法既可以作为堆栈的出栈(等价于pop(e)
),又可以作为队列的出队(等价于remove()
),因为它们在该对象中
先出的都是首位元素
2. 本方法调用的是popFirst()
,只是在调用后判断了是否有移除元素,若未移除元素(队列无元素)则抛出NoSuchElementException
异常,而直接调用popFirst()
返回null
3. 本方法会返回被移除的元素
// 源码
public E removeFirst() {
E x = pollFirst();
if (x == null)
throw new NoSuchElementException();
return x;
}
// 示例
@Test
void contextLoads() {
Object[] elements = new Object[]{1,2,3,4,8};
// 正常移除
ArrayDeque arrayDeque1 = new ArrayDeque();
arrayDeque1.addAll(Arrays.asList(elements));
// 1
System.out.println(arrayDeque1.removeFirst());
// [2, 3, 4, 8]
System.out.println(arrayDeque1);
// pollFirst() 空队列移除
ArrayDeque arrayDeque2 = new ArrayDeque();
// null
System.out.println(arrayDeque2.pollFirst());
// []
System.out.println(arrayDeque2);
// removeFirst() 空队列移除
ArrayDeque arrayDeque3 = new ArrayDeque();
// NoSuchElementException
System.out.println(arrayDeque3.removeFirst());
System.out.println(arrayDeque3);
}
06.移除首位元素(不判空) pollFirst()
注意:
1. 本方法基本等价于removeFirst()
,只是本方法若队列为空返回null
,而removeFirst()
抛出异常
2. 本方法等效于队列的poll()
方法
3. 本方法会返回被移除的元素
// 源码
public E pollFirst() {
int h = head;
@SuppressWarnings("unchecked")
// 获取移除的元素便于返回
E result = (E) elements[h];
// 如果当前队列为空此处为null(双端队列不能储存null值),直接返回null
if (result == null)
return null;
// 设置被移除的元素为null(数组储存需要设置为null)
elements[h] = null;
// 计算新的头索引
head = (h + 1) & (elements.length - 1);
return result;
}
// 解析示例
@Test
void contextLoads() {
Object[] elements = new Object[]{1,2,3,4,8,7,8,5};
int head = 7;
int h = head;
Integer result = (Integer) elements[h];
elements[h] = null;
// (7 + 1) & (8 - 1) = (8 & 7) = (1000 & 0111 = 0000) = 0
head = (h + 1) & (elements.length - 1);
// 5
System.out.println(result);
// [1, 2, 3, 4, 8, 7, 8, null]
System.out.println(Arrays.toString(elements));
}
// 方法示例
@Test
void contextLoads() {
Object[] elements = new Object[]{1,2,3,4,8};
// 正常移除
ArrayDeque arrayDeque1 = new ArrayDeque();
arrayDeque1.addAll(Arrays.asList(elements));
// 1
System.out.println(arrayDeque1.pollFirst());
// [2, 3, 4, 8]
System.out.println(arrayDeque1);
// pollFirst() 空队列移除
ArrayDeque arrayDeque2 = new ArrayDeque();
// null
System.out.println(arrayDeque2.pollFirst());
// []
System.out.println(arrayDeque2);
// removeFirst() 空队列移除
ArrayDeque arrayDeque3 = new ArrayDeque();
// NoSuchElementException
System.out.println(arrayDeque3.removeFirst());
System.out.println(arrayDeque3);
}
07.移除末尾元素(判空) removeLast()
注意:
1. 本方法调用的是pollLast()
,只是在调用后判断了是否有移除元素,若未移除元素(队列无元素)则抛出NoSuchElementException
异常,而直接调用pollLast()
返回null
2. 本方法会返回移除的元素
// 源码
public E removeLast() {
E x = pollLast();
if (x == null)
throw new NoSuchElementException();
return x;
}
// 示例
@Test
void contextLoads() {
Object[] elements = new Object[]{1,2,3,4,8};
// 正常移除
ArrayDeque arrayDeque1 = new ArrayDeque();
arrayDeque1.addAll(Arrays.asList(elements));
// 8
System.out.println(arrayDeque1.removeLast());
// [1, 2, 3, 4]
System.out.println(arrayDeque1);
// pollLast() 空队列移除
ArrayDeque arrayDeque2 = new ArrayDeque();
// null
System.out.println(arrayDeque2.pollLast());
// []
System.out.println(arrayDeque2);
// removeLast() 空队列移除
ArrayDeque arrayDeque3 = new ArrayDeque();
// NoSuchElementException
System.out.println(arrayDeque3.removeLast());
System.out.println(arrayDeque3);
}
08.移除末尾元素(不判空) pollLast()
注意:
1. 本方法基本等价于removeLast()
,只是本方法若队列为空返回null
,而removeLast()
抛出异常
2. 本方法返回被移除的元素
// 源码
public E pollLast() {
// 计算新的尾索引
int t = (tail - 1) & (elements.length - 1);
@SuppressWarnings("unchecked")
// 获取移除的元素便于返回
E result = (E) elements[t];
// 若当前数组为空此处为null(双端队列不能储存null值),直接返回null值
if (result == null)
return null;
// 设置被移除的元素为null(数组储存需要设置为null)
elements[t] = null;
tail = t;
return result;
}
// 解析示例
@Test
void contextLoads() {
Object[] elements = new Object[]{null,2,3,4,8,7,8,5};
int tail = 0;
// (0 - 1) & (8 - 1) = -1 & 7 = 1111 & 0111 = 0111 = 7
int t = (tail - 1) & (elements.length - 1);
@SuppressWarnings("unchecked")
Integer result = (Integer) elements[t];
elements[t] = null;
tail = t;
// 5
System.out.println(result);
// [null, 2, 3, 4, 8, 7, 8, null]
System.out.println(Arrays.toString(elements));
}
// 方法示例
@Test
void contextLoads() {
Object[] elements = new Object[]{1,2,3,4,8};
// 正常移除
ArrayDeque arrayDeque1 = new ArrayDeque();
arrayDeque1.addAll(Arrays.asList(elements));
// 8
System.out.println(arrayDeque1.pollLast());
// [1, 2, 3, 4]
System.out.println(arrayDeque1);
// pollLast() 空队列移除
ArrayDeque arrayDeque2 = new ArrayDeque();
// null
System.out.println(arrayDeque2.pollLast());
// []
System.out.println(arrayDeque2);
// removeLast() 空队列移除
ArrayDeque arrayDeque3 = new ArrayDeque();
// NoSuchElementException
System.out.println(arrayDeque3.removeLast());
System.out.println(arrayDeque3);
}
09.获取头元素(判空) getFirst()
注意:
1. 本方法若队列为空会抛出NoSuchElementException
异常
2. 本方法等效于队列的element()
方法
3. 本方法等价于peekFirst()
,唯一的区别是若队列为空,peekFirst()
直接返回null
,而本方法会抛出异常
// 源码
public E getFirst() {
@SuppressWarnings("unchecked")
E result = (E) elements[head];
if (result == null)
throw new NoSuchElementException();
return result;
}
// 错误示例
@Test
void contextLoads() {
ArrayDeque arrayDeque = new ArrayDeque();
// NoSuchElementException
System.out.println(arrayDeque.getFirst());
}
// 方法示例
@Test
void contextLoads() {
Object[] elements = new Object[]{1,2,3,4,8,18,19,11};
ArrayDeque arrayDeque = new ArrayDeque(Arrays.asList(elements));
// 1
System.out.println(arrayDeque.getFirst());
// [1, 2, 3, 4, 8, 18, 19, 11]
System.out.println(arrayDeque);
}
10.获取头元素(不判空) peekFirst()
注意:
1. 本方法等价于getFirst()
,唯一的区别是若队列为空,getFirst()
会抛出异常,而本方法是直接返回null
2. 本方法等效于队列的peek()
方法
3. 可以根据本方法的返回值判断对象是否为空(为空会返回null),但不建议这样使用
// 源码
public E peekFirst() {
// 此处若当前数组位空返回null
return (E) elements[head];
}
// 方法示例
@Test
void contextLoads() {
ArrayDeque arrayDeque = new ArrayDeque();
// null
System.out.println(arrayDeque.peekFirst());
}
11.获取末尾元素(判空) getLast()
注意:本方法等价于peekLast()
方法,区别是若当前队列为空,本方法抛出NoSuchElementException
异常,而peekLast()
返回null
// 源码
public E getLast() {
// 尾索引是下一个元素储存的位置,需计算尾索引的前一个索引位获取元素
E result = (E) elements[(tail - 1) & (elements.length - 1)];
if (result == null)
throw new NoSuchElementException();
return result;
}
// 解析示例
@Test
void contextLoads() {
Object[] elements = new Object[]{null,2,3,4,8,7,8,5};
int tail = 0;
// (0 - 1) & (8 - 1) = -1 & 7 = 1111 & 0111 = 0111 = 7
Integer result = (Integer) elements[(tail - 1) & (elements.length - 1)];
// 5
System.out.println(result);
}
// 错误示例
@Test
void contextLoads() {
ArrayDeque arrayDeque = new ArrayDeque();
// NoSuchElementException
System.out.println(arrayDeque.getLast());
}
// 方法示例
@Test
void contextLoads(){
Object[] elements = new Object[]{1,2,3,4,8,18,19};
ArrayDeque arrayDeque = new ArrayDeque(Arrays.asList(elements));
// 19
System.out.println(arrayDeque.getLast());
// [1, 2, 3, 4, 8, 18, 19]
System.out.println(arrayDeque);
}
12.获取末尾元素(不判空) peekLast()
注意:本方法等价于getLast()
方法,区别是若当前队列为空,本方法返回null
,而getLast()
抛出异常
// 源码
public E peekLast() {
// 尾索引是下一个元素储存的位置,需计算尾索引的前一个索引位获取元素
return (E) elements[(tail - 1) & (elements.length - 1)];
}
// 解析示例
@Test
void contextLoads() {
Object[] elements = new Object[]{null,2,3,4,8,7,8,5};
int tail = 0;
// (0 - 1) & (8 - 1) = -1 & 7 = 1111 & 0111 = 0111 = 7
// 5
System.out.println(elements[(tail - 1) & (elements.length - 1)]);
}
// 方法示例
@Test
void contextLoads() {
ArrayDeque arrayDeque = new ArrayDeque();
// null
System.out.println(arrayDeque.peekLast());
}
13.移除首个指定的元素 removeFirstOccurrence(Object o)
参数:o
—— 要移除的元素
注意:本方法循环储存队列元素的数组一一匹配,找到则移除并返回true
// 源码
public boolean removeFirstOccurrence(Object o) {
// 若队列为空则返回false
if (o == null)
return false;
int mask = elements.length - 1;
int i = head;
Object x;
while ( (x = elements[i]) != null) {
if (o.equals(x)) {
// 移除此元素
delete(i);
// 移除首个匹配元素时直接返回true
return true;
}
// 计算后一个索引位置(可能小于头索引,需要计算不能直接+1)
i = (i + 1) & mask;
}
return false;
}
// 方法示例
@Test
void contextLoads() {
Object[] elements = new Object[]{1,2,8,4,8,18,19};
ArrayDeque arrayDeque = new ArrayDeque(Arrays.asList(elements));
arrayDeque.removeFirstOccurrence(8);
// [1, 2, 4, 8, 18, 19]
System.out.println(arrayDeque);
}
14.移除最后一个指定的元素 removeLastOccurrence(Object o)
参数:o
—— 要移除的元素
注意:
1. 本方法循环储存队列元素的数组一一匹配,找到则移除并返回true
2. 因为本队列为双端队列,因此并不需要从头索引往尾索引循环或获取元素数递减循环,直接从尾索引往头索引循环匹配首个元素即可
// 源码
public boolean removeLastOccurrence(Object o) {
// 若队列为空则返回false
if (o == null)
return false;
int mask = elements.length - 1;
// 获取最后一位元素索引
int i = (tail - 1) & mask;
Object x;
while ( (x = elements[i]) != null) {
if (o.equals(x)) {
// 移除此元素
delete(i);
// 移除首个匹配元素时直接返回true(此处是从尾索引往头索引循环,移除首个匹配即可)
return true;
}
// 计算前一个索引位置(可能小于头索引,需要计算不能直接-1)
i = (i - 1) & mask;
}
return false;
}
// 方法示例
@Test
void contextLoads() {
Object[] elements = new Object[]{1,2,8,4,8,18,19};
ArrayDeque arrayDeque = new ArrayDeque(Arrays.asList(elements));
arrayDeque.removeLastOccurrence(8);
// [1, 2, 8, 4, 18, 19]
System.out.println(arrayDeque);
}
公有队列方法
01.入队 add(E e)
参数:e
—— 要入队的元素
注意:本方法的实现与offerFirst()
完全一致,若指定元素为null
会抛出异常
// 源码
public boolean add(E e) {
addLast(e);
// 本方法有返回,不抛出异常就返回true
return true;
}
// 错误示例
@Test
void tes1t(){
ArrayDeque arrayDeque = new ArrayDeque();
// NullPointerException
arrayDeque.add(null);
System.out.println(arrayDeque);
}
// 方法示例
@Test
void contextLoads() {
Object[] elements = new Object[]{1,2,8,4,8,18,19};
ArrayDeque arrayDeque = new ArrayDeque(Arrays.asList(elements));
arrayDeque.add(8);
// [1, 2, 8, 4, 8, 18, 19, 8]
System.out.println(arrayDeque);
}
02.入队 offer(E e)
参数:e
—— 要入队的元素
注意:因为add(E e)
方法实现与offerFirst()
完全一致,本方法调用的又是offerFirst()
,因此本方法与add(E e)
没有区别
// 源码
public boolean offer(E e) {
// offerLast(e) 方法中已经添加了返回
return offerLast(e);
}
// 错误示例
@Test
void tes1t(){
ArrayDeque arrayDeque = new ArrayDeque();
// NullPointerException
arrayDeque.offer(null);
System.out.println(arrayDeque);
}
// 方法示例
@Test
void contextLoads() {
Object[] elements = new Object[]{1,2,8,4,8,18,19};
ArrayDeque arrayDeque = new ArrayDeque(Arrays.asList(elements));
arrayDeque.offer(8);
// [1, 2, 8, 4, 8, 18, 19, 8]
System.out.println(arrayDeque);
}
03.出队(判空) remove()
注意:该方法会返回出队的元素
// 源码
public E remove() {
return removeFirst();
}
// 空队列示例
@Test
void contextLoads() {
ArrayDeque arrayDeque = new ArrayDeque();
// NoSuchElementException
arrayDeque.remove();
System.out.println(arrayDeque);
}
// 方法示例
@Test
void contextLoads() {
Object[] elements = new Object[]{1,2,8,4,8,18,19};
ArrayDeque arrayDeque = new ArrayDeque(Arrays.asList(elements));
arrayDeque.remove();
// [2, 8, 4, 8, 18, 19]
System.out.println(arrayDeque);
}
04.出队(不判空) poll()
注意:该方法会返回出队的元素
// 源码
public E poll() {
return pollFirst();
}
// 空队列示例
@Test
void tes1t(){
ArrayDeque arrayDeque = new ArrayDeque();
// null
System.out.println(arrayDeque.poll());
// []
System.out.println(arrayDeque);
}
// 方法示例
@Test
void contextLoads() {
Object[] elements = new Object[]{1,2,8,4,8,18,19};
ArrayDeque arrayDeque = new ArrayDeque(Arrays.asList(elements));
// 1
System.out.println(arrayDeque.poll());
// [2, 8, 4, 8, 18, 19]
System.out.println(arrayDeque);
}
05.获取首位元素(判空) element()
// 源码
public E element() {
return getFirst();
}
// 空队列示例
@Test
void tes1t(){
ArrayDeque arrayDeque = new ArrayDeque();
// NoSuchElementException
System.out.println(arrayDeque.element());
}
// 方法示例
@Test
void contextLoads() {
Object[] elements = new Object[]{1,2,8,4,8,18,19};
ArrayDeque arrayDeque = new ArrayDeque(Arrays.asList(elements));
// 1
System.out.println(arrayDeque.element());
}
06.获取首位元素(不判空) peek()
注意:本方法队列和堆栈通用
// 源码
public E peek() {
return peekFirst();
}
// 空队列示例
@Test
void tes1t(){
ArrayDeque arrayDeque = new ArrayDeque();
// null
System.out.println(arrayDeque.peek());
}
// 方法示例
@Test
void contextLoads() {
Object[] elements = new Object[]{1,2,8,4,8,18,19};
ArrayDeque arrayDeque = new ArrayDeque(Arrays.asList(elements));
// 1
System.out.println(arrayDeque.peek());
}
公有堆栈方法
01.入栈push(E e)
参数:e
—— 要入栈的元素
注意:与队列方法不同,本方法无返回,而队列方法有返回(不报错则返回true)
// 源码
public void push(E e) {
addFirst(e);
}
// 示例
@Test
void contextLoads() {
Object[] elements = new Object[]{1,2,8,4,8,18,19};
ArrayDeque arrayDeque = new ArrayDeque(Arrays.asList(elements));
arrayDeque.push(11);
// [11, 1, 2, 8, 4, 8, 18, 19]
System.out.println(arrayDeque);
}
02.出栈pop(E e)
参数:e
—— 要出栈的元素
注意:
1. 该方法会返回出栈的元素
2. 堆栈方法只有一个出栈方法,若堆栈为空,会抛出NoSuchElementException
异常
// 源码
public E pop() {
return removeFirst();
}
// 空队列示例
@Test
void contextLoads(){
ArrayDeque arrayDeque = new ArrayDeque();
// NoSuchElementException
arrayDeque.pop();
System.out.println(arrayDeque.peek());
}
// 方法示例
@Test
void contextLoads() {
Object[] elements = new Object[]{1,2,8,4,8,18,19};
ArrayDeque arrayDeque = new ArrayDeque(Arrays.asList(elements));
arrayDeque.pop();
// [2, 8, 4, 8, 18, 19]
System.out.println(arrayDeque);
}
公有集合方法
01.队列元素个数 size()
// 源码
public int size() {
return (tail - head) & (elements.length - 1);
}
// 解析示例
@Test
void tes1t(){
Object[] elements = new Object[]{1,null,2,8,4,8,18,19};
int head = 2;
int tail = 1;
// (1 - 2) & (8 - 1) = -1 & 7 = 1111 & 0111 = 7
System.out.println((tail - head) & (elements.length - 1));
}
// 方法示例
@Test
void contextLoads() {
Object[] elements = new Object[]{1,2,8,4,8,18,19};
ArrayDeque arrayDeque = new ArrayDeque(Arrays.asList(elements));
// 7
System.out.println(arrayDeque.size());
}
02.队列是否为空 isEpty()
// 源码
public boolean isEmpty() {
return head == tail;
}
// 方法示例
@Test
void contextLoads() {
Object[] elements = new Object[]{1,2,8,4,8,18,19};
ArrayDeque arrayDeque = new ArrayDeque(Arrays.asList(elements));
// false
System.out.println(arrayDeque.isEmpty());
}
03.队列是否包含某一元素 contains(Object o)
参数:o
—— 要判断的元素
// 源码
public boolean contains(Object o) {
if (o == null)
return false;
int mask = elements.length - 1;
int i = head;
Object x;
// 循环对元素一一匹配,只要找到第一个匹配的则返回true
while ( (x = elements[i]) != null) {
if (o.equals(x))
return true;
i = (i + 1) & mask;
}
return false;
}
// 方法示例
@Test
void contextLoads() {
Object[] elements = new Object[]{1,2,8,4,8,18,19};
ArrayDeque arrayDeque = new ArrayDeque(Arrays.asList(elements));
// true
System.out.println(arrayDeque.contains(8));
}
04.移除队列中指定元素 remove(Object o)
参数:o
—— 要移除的元素
注意:本方法移除的是从头索引开始首个
匹配的元素
// 源码
public boolean remove(Object o) {
return removeFirstOccurrence(o);
}
// 方法示例
@Test
void contextLoads() {
Object[] elements = new Object[]{1,2,8,4,8,18,19};
ArrayDeque arrayDeque = new ArrayDeque(Arrays.asList(elements));
// true
System.out.println(arrayDeque.remove(8));
// [1, 2, 4, 8, 18, 19]
System.out.println(arrayDeque);
}
05.清空队列 clear()
注意:当头尾索引一致,队列为空不处理也不报错
// 源码
public void clear() {
int h = head;
int t = tail;
if (h != t) {
// 先重置头尾索引为0
head = tail = 0;
int i = h;
int mask = elements.length - 1;
// 当元素索引不为0时循环,先运行后判断便于将索引0的元素也移除,不使用while或for循环为了防止下标越界
do {
elements[i] = null;
i = (i + 1) & mask;
} while (i != t);
}
}
// 方法示例
@Test
void contextLoads() {
Object[] elements = new Object[]{1,2,8,4,8,18,19};
ArrayDeque arrayDeque = new ArrayDeque(Arrays.asList(elements));
arrayDeque.clear();
// []
System.out.println(arrayDeque);
}
06.队列转换为数组 toArray()
注意:调用私有的复制数组的方法,从头索引开始,到尾索引结束,不包含尾索引(尾索引为下一次新增元素储存的位置,除非数组已满否则为null)
// 源码
public Object[] toArray() {
return copyElements(new Object[size()]);
}
// 方法示例
@Test
void contextLoads() {
Object[] elements = new Object[]{1,2,8,4,8,18,19};
// [1, 2, 8, 4, 8, 18, 19, null]
// head = 0 tail = 7
ArrayDeque arrayDeque = new ArrayDeque(Arrays.asList(elements));
// [null, 2, 8, 4, 8, 18, 19, null]
// head = 1 tail = 7
arrayDeque.removeFirst();
// [null, 2, 8, 4, 8, 18, 19, 19]
// head = 1 tail = 0
arrayDeque.add(19);
// [null, null, 8, 4, 8, 18, 19, 19]
// head = 2 tail = 0
arrayDeque.removeFirst();
// [1, null, 8, 4, 8, 18, 19, 19]
// head = 2 tail = 1
arrayDeque.add(1);
Object[] array = arrayDeque.toArray();
// [8, 4, 8, 18, 19, 19, 1]
System.out.println(Arrays.toString(array));
}
07.队列转换到指定数组 toArray(T[] a)
参数:a
—— 接收拷贝数据的数组
注意:
1. 本方法虽然提供了接收数据的数组,但若容量不够,返回的其实并不是原数组,而是重新创建了一个,提供的数组主要的作用是确定类型
2. 若指定数组原来有元素并不会清空会直接覆盖,若指定数组元素多于要拷贝的元素,为了区分会将拷贝的元素后一位元素设置为null
,因此不建议使用已有元素的数组接收拷贝数据(除非业务需要)
// 源码
@SuppressWarnings("unchecked")
public <T> T[] toArray(T[] a) {
int size = size();
// 指定数组容量不够创建一个新的
if (a.length < size)
a = (T[])java.lang.reflect.Array.newInstance(
a.getClass().getComponentType(), size);
// 拷贝元素,按照头索引到尾索引的顺序拷贝
copyElements(a);
// 若原数组容量足够,且指定数组中原来有元素,为了区分会将拷贝的元素后一位元素设置为null
if (a.length > size)
a[size] = null;
return a;
}
// 示例1:指定数组容量不足
@Test
void contextLoads(){
Integer[] i = new Integer[8];
// [Ljava.lang.Integer;@15dc339f
System.out.println(i);
Integer[] elements = new Integer[]{1,2,8,4,8,18,19,0,66,97};
ArrayDeque arrayDeque = new ArrayDeque(Arrays.asList(elements));
Object[] objects = arrayDeque.toArray(i);
// 并非指定数组对象 [Ljava.lang.Integer;@6cd56321
System.out.println(objects);
}
// 示例2:指定数组容量足够
@Test
void contextLoads(){
Integer[] i = new Integer[16];
// [Ljava.lang.Integer;@15dc339f
System.out.println(i);
Integer[] elements = new Integer[]{1,2,8,4,8,18,19,0,66,97};
ArrayDeque arrayDeque = new ArrayDeque(Arrays.asList(elements));
Object[] objects = arrayDeque.toArray(i);
// 指定数组对象 [Ljava.lang.Integer;@15dc339f
System.out.println(objects);
}
// 示例3:指定数组有元素且多于拷贝元素
@Test
void contextLoads(){
Integer[] i = new Integer[]{1,1,1,1,1,1,1,1,1,1,1,1,1,1};
// [Ljava.lang.Integer;@15dc339f
System.out.println(i);
Integer[] elements = new Integer[]{1,2,8,4,8,18,19,0,66,97};
ArrayDeque arrayDeque = new ArrayDeque(Arrays.asList(elements));
Object[] objects = arrayDeque.toArray(i);
// 指定数组对象,null分隔原数据 [Ljava.lang.Integer;@15dc339f
System.out.println(objects);
// [1, 2, 8, 4, 8, 18, 19, 0, 66, 97, null, 1, 1, 1]
System.out.println(Arrays.toString(i));
}
08.迭代器(正常顺序) iterator()
// 源码
public Iterator<E> iterator() {
// 对象实现参考内部类解析
return new DeqIterator();
}
// 方法示例
@Test
void contextLoads() {
Integer[] elements = new Integer[]{1,2,8,4,8,18,19,0,66,97};
ArrayDeque arrayDeque = new ArrayDeque(Arrays.asList(elements));
System.out.println("正序迭代器:");
Iterator iterator = arrayDeque.iterator();
while ((iterator.hasNext())){
// 1 2 8 4 8 18 19 0 66 97
System.out.print(iterator.next() + " ");
}
}
09.迭代器(反向顺序) descendingIterator()
// 源码
public Iterator<E> descendingIterator() {
// 对象实现参考内部类解析
return new DescendingIterator();
}
// 方法示例
@Test
void contextLoads() {
Integer[] elements = new Integer[]{1,2,8,4,8,18,19,0,66,97};
ArrayDeque arrayDeque = new ArrayDeque(Arrays.asList(elements));
System.out.println("逆序迭代器:");
Iterator iterator1 = arrayDeque.descendingIterator();
while (iterator1.hasNext()){
// 97 66 0 19 18 8 4 8 2 1
System.out.print(iterator1.next() + " ");
}
}
10.可拆分迭代器 spliterator()
// 源码
public Spliterator<E> spliterator() {
return new DeqSpliterator<E>(this, -1, -1);
}
公有对象方法
01.克隆当前对象 clone()
注意:本克隆方法使用了Object
的克隆方法,本方法是浅克隆
,如果元素是实例对象,克隆的是对象的指向,而不是直接新建了对象,修改被克隆或克隆生成的元素对象,会对两个对象都有影响
// 源码
public ArrayDeque<E> clone() {
try {
@SuppressWarnings("unchecked")
// 默认调用Object
ArrayDeque<E> result = (ArrayDeque<E>) super.clone();
result.elements = Arrays.copyOf(elements, elements.length);
return result;
} catch (CloneNotSupportedException e) {
throw new AssertionError();
}
}
// 示例:克隆队列的元素为对象
@Test
void contextLoads() throws CloneNotSupportedException {
StringBuilder sb = new StringBuilder("1");
StringBuilder sb1 = new StringBuilder("2");
StringBuilder sb2 = new StringBuilder("3");
StringBuilder sb3 = new StringBuilder("4");
StringBuilder sb4 = new StringBuilder("5");
StringBuilder sb5 = new StringBuilder("6");
StringBuilder[] elements = new StringBuilder[]{sb,sb1,sb2,sb3,sb4,sb5};
ArrayDeque<StringBuilder> arrayDeque = new ArrayDeque(Arrays.asList(elements));
ArrayDeque<StringBuilder> clone = arrayDeque.clone();
// [1, 2, 3, 4, 5, 6]
System.out.println(clone);
sb.append("+");
// [1+, 2, 3, 4, 5, 6]
System.out.println(arrayDeque);
// [1+, 2, 3, 4, 5, 6]
System.out.println(clone);
}
序列化
01.序列号
private static final long serialVersionUID = 2340985798034038923L;
02.写入writeObject(java.io.ObjectOutputStream s)
参数:s
—— 对象转换为的二进制流
private void writeObject(java.io.ObjectOutputStream s)
throws java.io.IOException {
s.defaultWriteObject();
// 写入元素数量
s.writeInt(size());
// 循环写入元素
int mask = elements.length - 1;
// 循环将元素一个个写入对象
for (int i = head; i != tail; i = (i + 1) & mask)
s.writeObject(elements[i]);
}
03.读取readObject(java.io.ObjectOutputStream s)
参数:s
—— 对象转换为的二进制流
private void readObject(java.io.ObjectInputStream s)
throws java.io.IOException, ClassNotFoundException {
s.defaultReadObject();
// 读取元素个数
int size = s.readInt();
// 读取队列容量
int capacity = calculateSize(size);
// 根据容量创建储存元素的数组对象
// SharedSecrets是用来获取Java栈帧中存储的类信息的(日志、序列化等需要获取JVM储存的类信息时使用)
SharedSecrets.getJavaOISAccess().checkArray(s, Object[].class, capacity);
// 根据元素个数初始化数组
allocateElements(size);
// 设置头尾索引
head = 0;
tail = size;
// 循环将元素存入要读取出的数组
for (int i = 0; i < size; i++)
elements[i] = s.readObject();
}
总结
数据结构
- 本类中存在多个增删读的方法,可以根据需要使用的数据结构和想要的校验/返回值配套使用
- 本方法主要用于实现队列或堆栈,因此没有修改的方法
- 如需将本类作为堆栈使用,可以直接使用
peek()
作为获取元素的方法,将push(E e)
和pop(E e)
作为入栈、出栈的方法,但是pop(E e)
是用来出栈指定元素的,正常的出栈可以使用poll()
或remove()
,当然使用removeFirst()
或pollFirst()
也可以 - 如需将本类作为队列使用,可以直接使用
add(E e)
、offer(E e)
作为入队方法,将poll()
或remove()
作为出队方法,将element()
或peek()
作为获取一个元素方法 - 当然不想使用方法准备好的堆栈方法或队列方法,使用公共方法自己配套使用也可以,但尤其要注意入队和入栈的方法,这两个方法是决定对象实现的数据结构的关键
- 对于公有方法,简单来说,以一般集合命名方式的新增方法(
add
)没有返回值,以一般集合命名方式的删除方法(remove
)和获取元素方法(get
)会判空;而其他的方法则相反(有返回值,不会判空),更加便利和约束较小一点 - 对于队列方法,基本与第
6
点一致,除了element()
方法,可以将它看作get()
方法 - 对于堆栈方法,虽然命名不同,但其规则与普通集合规则一致
双端队列
- 通过名字可以看出,本类与普通数组不同移除主要为双端,两端都可进行操作,而非一般的数组只能从后面增删
- 因为其双端的特性,本类主要用来实现一些数据结构,如果仅用于储存元素使用
ArrayList
、HashMap
等都可以 - 本类中最重要的就是后尾索引两个参数,他们实现了双端的特性,也因此本类在一些方面效率和空间利用率会较高
- 本类为了便于实现不同的数据结构遍历,也提供的正向和反向两种迭代器
- 也因为本类主要用来实现不同的数据结构,因此本类并未提供修改元素的方法,毕竟队列、堆栈这种数据结构的使用主要因为它的存取值顺序特性
—— END ——