1. class 简介
ArrayDeque,顾名思义,就是由数组表示的双端队列,具有队列的所有方法,大部分方法都可以将时间复杂度均摊成常量时间,一般认为它比LinkedList表示的队列和栈的性能要好。
2. class内部原理及特点
- 不是线程安全的。
- 不允许向其中添加null。
- 内部是一个循环数组,由头尾下标控制有效数据的出入。数组容量一定为2的幂(方便用位与&代替%取余运算),初始化时不指定容量则默认为16,最小容量为8,当头尾下标相等时,则会翻倍扩容,创建一个新的数组,使用System.arraycopy移动数据。
- 文档上说ArrayDeque比LinkedList作队列和栈时要快,由于LinkedList每次进行头尾添加和删除操作时都会分配新内存或者丢弃内存,而ArrayDeque只是简单的移动head和tail下标,只有在扩容时才会新分配内存,而且ArrayDeque没有trimToSize瘦身方法,所以不会有丢弃内存的情况,另外内部还使用了大量的位运算,速度很快。并且作为数组其具备索引查找的先天优势(虽然作为队列和栈时不建议用这么做),惟一开销较大的就是扩容移动数据。
- 其迭代器是快速失败的,在其迭代器创建之后调用非迭代器中的方法对容器的内部结构进行修改都会抛出ConcurrentModificationException。
3. class源码细节分析
allocateElements
/* 分配2的幂的容量 */
private void allocateElements(int numElements) {
int initialCapacity = MIN_INITIAL_CAPACITY;
//如果指定容量小于最小容量,则直接为最小容量
if (numElements >= initialCapacity) {//保证会得到比指定容量大的2的幂
initialCapacity = numElements;
initialCapacity |= (initialCapacity >>> 1);
initialCapacity |= (initialCapacity >>> 2);
initialCapacity |= (initialCapacity >>> 4);
initialCapacity |= (initialCapacity >>> 8);
initialCapacity |= (initialCapacity >>> 16);
initialCapacity++;
if (initialCapacity < 0)
initialCapacity >>>= 1;
}
elements = (E[]) new Object[initialCapacity];
}
doubleCapacity
/* 创建一个容量翻倍后的数组,将旧数组的数据复制过去 */
private void doubleCapacity() {
assert head == tail;//尾巴碰到头了才去扩容
int p = head;
int n = elements.length;
int r = n - p;
int newCapacity = n << 1;//X2
if (newCapacity < 0)//太大溢出了
throw new IllegalStateException("Sorry, deque too big");
Object[] a = new Object[newCapacity];
System.arraycopy(elements, p, a, 0, r);//先把队头到数组末尾之间的数据复制过去
System.arraycopy(elements, 0, a, r, p);//再把数组起始位置到队头之间的数据复制过去
elements = (E[])a;
head = 0;
tail = n;
}
copyElements
/* 将ArrayDeque的内置数组数据复制到参数数组中 */
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;
}
ArrayDeque对队列和栈的操作方法主要是以下几个:addFirst, addLast, pollFirst, pollLast。
addFirst和addLast
/* 在前面添加 */
public void addFirst(E e) {
if (e == null)
throw new NullPointerException();
//每次head改变的时候都要对长度取余&,保证循环
elements[head = (head - 1) & (elements.length - 1)] = e;
if (head == tail)
doubleCapacity();
}
/* 在后面添加 */
public void addLast(E e) {
if (e == null)
throw new NullPointerException();
elements[tail] = e;
//同样每次tail改变时都要对长度取余,保证循环
if ( (tail = (tail + 1) & (elements.length - 1)) == head)
doubleCapacity();
}
pollFirst和pollLast
/* 删除第一个元素 */
public E pollFirst() {
int h = head;
E result = elements[h];
if (result == null)
return null;
elements[h] = null;//GC
//保证循环
head = (h + 1) & (elements.length - 1);
return result;
}
/* 删除最后一个元素 */
public E pollLast() {
//保证循环
int t = (tail - 1) & (elements.length - 1);
E result = elements[t];
if (result == null)
return null;
elements[t] = null;
tail = t;
return result;
}
peekFirst和peekLast
/* 还有会抛出异常相同功能方法getFirst和getLast,一般只用不抛出异常的 */
public E peekFirst() {
return elements[head]; // elements[head] is null if deque empty
}
public E peekLast() {
return elements[(tail - 1) & (elements.length - 1)];
}
4. 总结
ArrayDeque是性能很好java集合。如果需要队列或栈,应当首选ArrayDeque。当队列使用时尽量使用offer, poll, peek等名字的方法,当栈使用时尽量使用push, pop, peek等名字的方法。