ArrayDeque源码解读
ArrayDeque
ArrayDeque和LinkedList是Deque的两个通用实现,在使用Queue时,由于Queue只是一个接口,因此创建Queue时也会使用ArrayDeque
为了实现在数组两端进行操作元素的需求,因此ArrayDeque使用循环数组作为底层数据结构,同时,ArrayDeque中定义了head和tail两个指针指向头和尾
因为是循环数组,所以head可能比tail大,head指向第一个有效元素,tail指向尾部第一个可以插入元素的空位。
插入元素
因为是循环数组,扩容比较棘手,因此扩容操作是在插入操作完成之后进行的。
tail永远执行一个空位置,所以插入操作永远是有空余位置的。
下标越界直接取余就行。
public void addFirst(E e) {
if (e == null)//不允许放入null
throw new NullPointerException();
elements[head = (head - 1) & (elements.length - 1)] = e;//2.下标是否越界
if (head == tail)//1.空间是否够用
doubleCapacity();//扩容
}
扩容操作
扩容操作的逻辑是申请一个更大的数组(两倍),然后使用System.arraycopy进行数据拷贝,拷贝的时候拷贝到新数组的头部。
private void doubleCapacity() {
assert head == tail;
int p = head;
int n = elements.length;
int r = n - p; // head右边元素的个数
int newCapacity = n << 1;//原空间的2倍
if (newCapacity < 0)
throw new IllegalStateException("Sorry, deque too big");
Object[] a = new Object[newCapacity];
//第一次复制head右边的元素
System.arraycopy(elements, p, a, 0, r);
//第二次复制head左边的元素
System.arraycopy(elements, 0, a, r, p);
elements = (E[])a;
head = 0;
tail = n;
}
删除操作
删除只需要挪动下标,然后原来位置置null(方便GC回收)
public E pollFirst() {
int h = head;
E result = elements[head];
if (result == null)
return null;
elements[h] = null;//方便GC回收
head = (head + 1) & (elements.length - 1);//下标越界处理
return result;
}
快速失败
ArrayDeque不是快速失败的集合,也不是安全失败的集合
ArrayDeque的迭代器是弱一致性的,不是快速失败的。它允许在迭代期间对集合进行修改而不会抛出异常,但这种修改可能不会被迭代器所反映出来。
快速失败和安全失败的区别
快速失败:使用modCount记录结构版本,发现接口修改直接抛出ConcurrentModificationException
安全失败:创建快照副本,保证迭代器迭代的时候不会受到结构修改的影响。
为什么不设计成一个快速失败的数据结构?
-
ArrayDeque的主要使用场景是队列或栈操作,这些操作通常是频繁的插入、删除、读取等基本操作。如果为ArrayDeque添加快速失败机制,会引入额外的性能开销,而这些开销在大多数场景下是不必要的。 -
ArrayDeque设计的主要目的是为单线程环境下的双端队列提供高效的实现。在这种场景下,快速失败的需求并不强烈,因为典型的使用模式是顺序操作,而不是并发访问。

浙公网安备 33010602011771号