简介
双端队列的实现一般有链表和循环数组两种,ArrayDeque就是使用循环数组实现的双端队列,其内部维护着一个数组,并且当数组空间不足时,会自动扩容,容量变为原来的2倍。
以下分析基于corretto-1.8.0_282版本。
继承关系
- 实现了Cloneable接口,可以通过.clone()方法克隆出一个实例。
- 实现了Serializable接口,可以被序列化。
- 实现了Deque接口,可以作为双端队列使用。
属性
elements
/**
* 储存元素的数组,大小始终为2的幂
* 因为使用循环数组,头尾指针的计算可能需要取模运算,
* 数组长度定为2的幂就可以改为位运算,速度会快一点
*/
transient Object[] elements;
head
/**
* 双端队列的头指针
* 总是指向位于队列头的元素
*/
transient int head;
tail
/**
* 双端队列的尾指针
* 总是指向队列尾的下一个待插入的位置
*/
transient int tail;
MIN_INITIAL_CAPACITY
/**
* 最小初始化容量
*/
private static final int MIN_INITIAL_CAPACITY = 8;
构造方法
ArrayDeque()
/**
* 使用默认容量(16)实例化ArrayDeque
*/
public ArrayDeque() {
elements = new Object[16];
}
ArrayDeque(int numElements)
/**
* 使用给定的容量实例化ArrayDeque,容量会设置为2的幂
*/
public ArrayDeque(int numElements) {
allocateElements(numElements);
}
/**
* 根据传入参数申请数组空间,参数会被调整为2的幂
*/
private void allocateElements(int numElements) {
elements = new Object[calculateSize(numElements)];
}
/**
* 将传入参数格式化为大于参数的最小的2的幂,同时处理边界情况
* 逻辑与HashMap中的基本类似,只不过本方法返回的值为大于numElements
*/
private static int calculateSize(int numElements) {
// 容量不能小于MIN_INITIAL_CAPACITY(8)
int initialCapacity = MIN_INITIAL_CAPACITY;
// 将传入参数调整为大于numElements的最小的2的幂
if (numElements >= initialCapacity) {
// 此处没有减一操作,所以最后计算的结果一定大于numElements
initialCapacity = numElements;
initialCapacity |= (initialCapacity >>> 1);
initialCapacity |= (initialCapacity >>> 2);
initialCapacity |= (initialCapacity >>> 4);
initialCapacity |= (initialCapacity >>> 8);
initialCapacity |= (initialCapacity >>> 16);
initialCapacity++;
// 超过Integer.MAX_VALUE,设置为2的30次方
if (initialCapacity < 0) // Too many elements, must back off
initialCapacity >>>= 1;// Good luck allocating 2 ^ 30 elements
}
return initialCapacity;
}
ArrayDeque(Collection<? extends E> c)
/**
* 根据传入集合实例化ArrayDeque,同时将传入集合的所有元素复制过来
*/
public ArrayDeque(Collection<? extends E> c) {
// 根据传入集合大小设置容量
allocateElements(c.size());
// 复制所有元素
addAll(c);
}
方法
addFirst(E e)
/**
* 队列头插入元素
* 先移动头指针再插入元素
*/
public void addFirst(E e) {
// 不能插入空元素
if (e == null)
throw new NullPointerException();
// 头指针减一再对数组长度取模,得到插入位置索引,在此所索引处插入元素
elements[head = (head - 1) & (elements.length - 1)] = e;
// 若头指针和尾指针重合,说明数组已满,进行扩容
if (head == tail)
doubleCapacity();
}
addLast(E e)
/**
* 队列尾插入元素
* 先插入元素再移动尾指针
*/
public void addLast(E e) {
// 不能插入空元素
if (e == null)
throw new NullPointerException();
// 尾指针处插入元素
elements[tail] = e;
// 尾指针加一,若尾指针和头指针重合,则需要进行扩容
if ( (tail = (tail + 1) & (elements.length - 1)) == head)
doubleCapacity();
}
pollFirst()
/**
* 移除队列头部元素
* 先弹出元素,再移动头指针,与插入时相反
*/
public E pollFirst() {
int h = head;
@SuppressWarnings("unchecked")
E result = (E) elements[h];
// 队列为空时返回null
if (result == null)
return null;
// 将此位置置为null
elements[h] = null;
// 移动头指针
head = (h + 1) & (elements.length - 1);
return result;
}
pollLast()
/**
* 移除队列尾部元素
* 先移动尾指针,再弹出元素,与插入时相反
*/
public E pollLast() {
// 移动尾指针
int t = (tail - 1) & (elements.length - 1);
@SuppressWarnings("unchecked")
E result = (E) elements[t];
// 队列为空,返回null
if (result == null)
return null;
// 此位置置为null
elements[t] = null;
tail = t;
return result;
}
getFirst()
/**
* 获取队列头元素
*/
public E getFirst() {
// 因为头指针总是指向队列头元素,所以直接返回头指针处的元素
@SuppressWarnings("unchecked")
E result = (E) elements[head];
if (result == null)
throw new NoSuchElementException();
return result;
}
getLast()
/**
* 获取队列尾元素
*/
public E getLast() {
// 因为尾指针总是指向队列尾元素的下一个待插入的位置,所以需要向左移动一位
@SuppressWarnings("unchecked")
E result = (E) elements[(tail - 1) & (elements.length - 1)];
if (result == null)
throw new NoSuchElementException();
return result;
}
removeFirstOccurrence(Object o)
/**
* 从头向尾查找给定的元素,如果存在,则将其删除
*/
public boolean removeFirstOccurrence(Object o) {
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);
return true;
}
i = (i + 1) & mask;
}
return false;
}
/**
* 删除给定索引处的元素
*/
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;
// 从i处将数组分为前后两半部分
final int front = (i - h) & mask;
final int back = (t - i) & mask;
// i需要满足 head <= i < tail的条件,分一下三种情况
// 1.
// ----------------
// h i t
//
// 2.
// ----------------
// t h i
//
// 3.
// ----------------
// i t h
if (front >= ((t - h) & mask))
throw new ConcurrentModificationException();
// 选择元素较少的一方进行移动
if (front < back) {
if (h <= i) {
// 对应上面图示的1、2两种情况
// 从队列头h到(i - h)处,所有元素向后移动一位
System.arraycopy(elements, h, elements, h + 1, front);
} else {
// 对应上面图示的情形3
// 先把从0到(i - 1)的元素向后移动一位,覆盖掉i处的元素,相当于删除了
System.arraycopy(elements, 0, elements, 1, i);
// 数组最后一个元素移动到索引0处
elements[0] = elements[mask];
// 最后把从h到(length - 2)处的元素都向后移动一位
System.arraycopy(elements, h, elements, h + 1, mask - h);
}
// 原来头指针指向的位置已经空出来了,将其置为null
elements[h] = null;
// 头指针向后移动一位
head = (h + 1) & mask;
return false;
} else {
if (i < t) {
// 对应上面图示的1、3两种情况
// 从(i + 1)到(tail - 1)的元素向前移动一位
System.arraycopy(elements, i + 1, elements, i, back);
// 移动尾指针
tail = t - 1;
} else {
// 对应上面图示的情形2
// 先把从(i + 1)到(length - 1 - i)处的元素向前移动一位
System.arraycopy(elements, i + 1, elements, i, mask - i);
// 再把索引0处的元素移动到数组末尾
elements[mask] = elements[0];
// 最后把从1到tail处的元素向前移动一位
System.arraycopy(elements, 1, elements, 0, t);
// 移动尾指针
tail = (t - 1) & mask;
}
return true;
}
}
/**
* 一些条件断言
*/
private void checkInvariants() {
// tail指针指向的位置必须永远是空位
assert elements[tail] == null;
// head == tail 说明队列为空,则头指针head指向位置必须为null
// 否则头指针和尾指针的前一个位置不能为空
assert head == tail ? elements[head] == null :
(elements[head] != null &&
elements[(tail - 1) & (elements.length - 1)] != null);
// 头指针前一个位置必须为null
assert elements[(head - 1) & (elements.length - 1)] == null;
}
removeLastOccurrence(Object o)
/**
* 从尾向头查找给定的元素,如果存在,则将其删除
*/
public boolean removeLastOccurrence(Object o) {
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);
return true;
}
i = (i - 1) & mask;
}
return false;
}
size()
/**
* 获取队列元素个数
*/
public int size() {
// 当head <= tail时
// 0 15
// ----------------
// h t
// size = t - h 当(h <= t)时
//
// 当tail < head时
// 0 15
// ----------------
// t h
// size = (t - 0) + (15 - h + 1)
// = t - h + 16 当(t < h)时
// (t - h + 16) % 16 = t - h
// size = (t - h) % length
return (tail - head) & (elements.length - 1);
}
isEmpty()
/**
* 头尾指针重合,队列为空
*/
public boolean isEmpty() {
return head == tail;
}
contains(Object o)
/**
* 判断队列中是否包含给定元素
*/
public boolean contains(Object o) {
if (o == null)
return false;
int mask = elements.length - 1;
int i = head;
Object x;
// 从队列头向队列尾遍历
while ( (x = elements[i]) != null) {
if (o.equals(x))
return true;
// 移动指针
i = (i + 1) & mask;
}
return false;
}
clear()
/**
* 清空队列
*/
public void clear() {
// 缓存头尾节点
int h = head;
int t = tail;
// 队列不为空
if (h != t) {
head = tail = 0;
int i = h;
int mask = elements.length - 1;
// 从队列头向队列尾遍历,清空所有元素
do {
elements[i] = null;
i = (i + 1) & mask;
} while (i != t);
}
}
总结
- ArrayDeque底层数据结构为循环数组,数组长度总是为2的幂,数组填满时会自动扩容,容量变为原来的2倍。
- ArrayDeque最小容量为16(大于MIN_INITIAL_CAPACITY(8)的最小的2的幂)。
- ArrayDeque可用作队列,双端队列,栈来使用。