继承关系

类名中的 Array姓氏,它是基于数组实现的。
类注释中,有句话引起了我的注意:
/**
* This class is likely to be faster than
* {@link Stack} when used as a stack, and faster than {@link LinkedList}
* when used as a queue.
*/
(Stack先不管)注释中后半句说,ArrayDeque作为队列时比LinkedList快,看看它是怎么办到的!
属性:
transient Object[] elements; //基于数组实现
transient int head; //头指针
transient int tail; //尾巴指针
构造器
private static final int MIN_INITIAL_CAPACITY = 8; // 默认大小
private static int calculateSize(int numElements) {
int initialCapacity = MIN_INITIAL_CAPACITY;
// Find the best power of two to hold elements.
// Tests "<=" because arrays aren't kept full.
if (numElements >= initialCapacity) {
initialCapacity = numElements;
initialCapacity |= (initialCapacity >>> 1);
initialCapacity |= (initialCapacity >>> 2);
initialCapacity |= (initialCapacity >>> 4);
initialCapacity |= (initialCapacity >>> 8);
initialCapacity |= (initialCapacity >>> 16);
initialCapacity++;
if (initialCapacity < 0) // Too many elements, must back off
initialCapacity >>>= 1;// Good luck allocating 2 ^ 30 elements
}
return initialCapacity;
}
// The main insertion and extraction methods are addFirst,
// addLast, pollFirst, pollLast. The other methods are defined in
// terms of these.
方法
以上注释有云,核心方法就4个,我们从add方法入手。
插入
- addFirst
public void addFirst(E e) {
if (e == null)
throw new NullPointerException();
elements[head = (head - 1) & (elements.length - 1)] = e; //关键
if (head == tail)
doubleCapacity();
}
head = (head - 1) & (elements.length - 1)
,玄机就在这里。如果你对1.8的HashMap
足够了解,就会知道hashmap的数组大小同样始终是2的次幂。其中很重要的一个原因就是:当lengh是2的次幂的时候,某数字 x 的操作 x & (length - 1)
等价于 x % length
,而对二进制的计算机来说 & 操作要比 % 操作效率更好!
而且head = (head - 1) & (elements.length - 1)
,(head初始值0)第一次就将head指针定位到数组末尾了。
画图分析下:

可见,head指针从后向前移动。
addLast
public void addLast(E e) {
if (e == null)
throw new NullPointerException();
elements[tail] = e;
if ( (tail = (tail + 1) & (elements.length - 1)) == head)
doubleCapacity();
}

addLast和addFirst原理相同,只是addLast控制tail指针,从前向后移动!
上图中再做一次add操作,指针将会重合。比如,再一次addFirst之后:

if (head == tail)
doubleCapacity(); //扩容触发
扩容
private void doubleCapacity() {
assert head == tail;
int p = head;
int n = elements.length;
int r = n - p; // number of elements to the right of p
int newCapacity = n << 1; //左移,等价乘2,依然保持2的次幂
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 = a;
head = 0;
tail = n;
}

通过数组拷贝和重新调整指针,完成了扩容。
至于pollFirst、pollLast是addFirst、addLast的相反操作,原理相似,不多做分析。
作为队列时,ArrayDeque效率为什么会比LinkedList更好?
我认为:因为只操作首尾元素,不会像ArrayList那样插入或删除中间元素,不会设计到大量的复制移动操作,并且基于数组。所以效率更高。
网友评论