ArrayDeque是一个以循环数组实现的双向队列。
官方是这么描述的:Deque 接口的大小可变数组的实现。数组双端队列没有容量限制;它们可根据需要增加以支持使用。它们不是线程安全的;在没有外部同步时,它们不支持多个线程的并发访问。禁止 null 元素。此类很可能在用作堆栈时快于 Stack,在用作队列时快于 LinkedList。
- ArrayDeque头
public class ArrayDeque<E> extends AbstractCollection<E>
implements Deque<E>, Cloneable, Serializable
上面是ArrayDeque定义信息,我们可以看出,它实现了AbstractCollection。
- 成员变量
transient Object[] elements; //定义一个数组,下面我们可以了解到,他的初始化操作在构造函数中,初始大小是16.
transient int head;//头指针,指向队列的头
transient int tail;//尾指针,指向队列的队尾
private static final int MIN_INITIAL_CAPACITY = 8;//数组最小容量。
下面我们看一下,构造函数。
- 构造函数1
public ArrayDeque() {
elements = new Object[16];//无参数构函,循环数组的默认大小是16.
}
- 构造函数2
public ArrayDeque(int numElements) {//这里用户提供了容量
allocateElements(numElements);//构函调用了这个函数,下面我们 来分析allocateElements();
}
private void allocateElements(int numElements) {
//先定义一个变量作为数组的最小容量,也就是上面定义变量:最小容量为8,如果用户输入的小于8,直接就用最小容量,来创建数组。
int initialCapacity = MIN_INITIAL_CAPACITY;
//如果大于8,则将initialCapacity 变成与其最接近并大于等于其自身的2的n次方幂
if (numElements >= initialCapacity) {
initialCapacity = numElements;
initialCapacity |= (initialCapacity >>> 1);
initialCapacity |= (initialCapacity >>> 2);
initialCapacity |= (initialCapacity >>> 4);
initialCapacity |= (initialCapacity >>> 8);
initialCapacity |= (initialCapacity >>> 16);
initialCapacity++;
//如果数组容量太大溢出,直接赋值为2的30次幂
//官方的注释:Too many elements, must back off,Good luck allocating 2 ^ 30 elements
if (initialCapacity < 0)
initialCapacity >>>= 1;
}
elements = new Object[initialCapacity];
}
我们看一下:
initialCapacity = numElements;
initialCapacity |= (initialCapacity >>> 1);
initialCapacity |= (initialCapacity >>> 2);
initialCapacity |= (initialCapacity >>> 4);
initialCapacity |= (initialCapacity >>> 8);
initialCapacity |= (initialCapacity >>> 16);
initialCapacity++;
假设我们输入的是17,initialCapacity 被赋值为17,17的二进制为10001
- 10001>>>1=1000,10001|1000=11001;
- 11001>>>2 =110,11001|110=11111;
- 11111>>>4=1,11111|1=11111;
- 11111>>>8=0,11111|0=11111;
11111>>>16=0,11111|0=11111;
最后initialCapacity++,也就是11111+1=100000转化为10进制也就是32。32也确实是大于17最近的2的n次幂。
这个算法的应用是很高效的,时间复杂度是O(1),要比用其他算法求效率要高。
- 构造函数3
public ArrayDeque(Collection<? extends E> c) {
allocateElements(c.size());//我们上面已经介绍过了
addAll(c);//把集合元素放在数组中
}
-部分成员方法
接下来,我们来看主要的添加操作:这个类中的操作主要有两个,一个从头加,一个是从尾加。
public void addFirst(E e) {
if (e == null)
throw new NullPointerException();//健壮性判断
elements[head = (head - 1) & (elements.length - 1)] = e;//每次向头添加都是,头指针自减并与数组长度-1进行与运算。
if (head == tail)
doubleCapacity();//如果循环数组加满,我们就扩容。
}
我们来分析 elements[head = (head - 1) & (elements.length - 1)] = e;
由于容量总是2的n次幂,所以总有以下规律
设a是一个整型变量,elements是2的n次幂的整型,那么有
当a=-1时
a&elements-1=elements-1 成立
当0<=a<=elements-1
a&elements-1=a
所以一开始head=0时,就向头中插入元素,实际插入的位置是数组的末尾。
我们在来看下面的doubleCapacity() 扩容方法。
private void doubleCapacity() {
assert head == tail;
int p = head;
int n = elements.length;
int r = n - p; // 数组右面的长度
int newCapacity = n << 1;//新容量是旧容量的2倍
if (newCapacity < 0)
throw new IllegalStateException("Sorry, deque too big");
Object[] a = new Object[newCapacity];
System.arraycopy(elements, p, a, 0, r);//拷贝数组右边,从head开始,拷贝r个长度,放在新数组从0开始的位置上。
System.arraycopy(elements, 0, a, r, p);
//拷贝数组左边,从0开始,拷贝p个长度,放在新数组从r开始的位置上。
elements = a;
head = 0;
tail = n;//head和tail指针重新初始化
}
我们来看从队尾加的方法
public void addLast(E e) {
if (e == null)
throw new NullPointerException();
elements[tail] = e;//需要注意的是,在队头插入结点是先走了一位在插入,这里队尾是现在当前位置插入,在“向前”走一位。
if ( (tail = (tail + 1) & (elements.length - 1)) == head)//如果头尾相遇就扩容,这个算法上面已经介绍过了, 和(elements.length - 1)进行与运算是为了尾指针能够自动循环。
doubleCapacity();
}
接下来是删除队头的节点
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; //将要poll的节点位置设置为空
head = (h + 1) & (elements.length - 1);//头指针向后挪一位
return result;
}
删除队尾结点
public E pollLast() {
int t = (tail - 1) & (elements.length - 1);//因为队尾插入的特点,尾指针现在指向的是尾结点的后一位,所以要tail-1来指向当前的尾结点。
@SuppressWarnings("unchecked")
E result = (E) elements[t];
if (result == null)
return null;
elements[t] = null;//将取出来的尾结点的位置,置空。
tail = t;
return result;
}
//只取头结点而不删除
@SuppressWarnings("unchecked")
public E peekFirst() {
// elements[head] is null if deque empty
return (E) elements[head];
}
//只取尾结点的值而不删除
@SuppressWarnings("unchecked")
public E peekLast() {
return (E) elements[(tail - 1) & (elements.length - 1)];
}