前言点击此处查看:
http://blog.csdn.net/wang7807564/article/details/79113195
ArrayDeque的数据结构要比PriorityQueue要简单得多,是通过数组来实现的。但是,ArrayDeque的特点是一个双端队列,既可以实现FIFO的Queue,也可以实现LIFO的Stack.
ArrayDeque虽然原理比较简单,但是其精髓是使用了位运算来加快计算效率。它的源代码如下:
/**
* Constructs an empty array deque with an initial capacity
* sufficient to hold 16 elements.
*/
public ArrayDeque() {
elements = new Object[16];
}
/**
* The minimum capacity that we'll use for a newly created deque.
* Must be a power of 2.
*/
private static final int MIN_INITIAL_CAPACITY = 8;
// ****** Array allocation and resizing utilities ******
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;
}
public ArrayDeque(int numElements) {
allocateElements(numElements);
}
private void allocateElements(int numElements) {
elements = new Object[calculateSize(numElements)];
}
从上面的代码可以看出来,使用calculateSize()方法可以自动计算出合适的2的n次幂的数值,用下面的代码作一个测试:
public class Main {
private static int getCapacity(int numElements) {
int 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;
}
public static void main(String[] args){
for(int i=0;i<5;i++){
System.out.println(i + " " + getCapacity(i));
}
}
}
输出结果是:
0 1
1 2
2 4
3 4
4 8
public int size() {
return (tail - head) & (elements.length - 1);
}
依然使用了位运算,之所以存储数组的长度都是2的n次幂的数值,是因为:
2^n - 1
计算之后的二进制结果前半部分都是0,后半部分都是1,能够起到类似掩码的作用,
例如下面的示意图:
实际上兼取绝对值与取模于一体了。
public void clear() {
int h = head;
int t = tail;
if (h != t) { // clear all cells
head = tail = 0;
int i = h;
int mask = elements.length - 1;
do {
elements[i] = null;
i = (i + 1) & mask;
} while (i != t);
}
}
清空队列并不将数组的数组元素数量再次置为初始值,只是将数组中对每个元素的引用置为null,这样可以被垃圾回收掉。
可以看到,在遍历队列中每个元素的时候,巧妙地运用了位运算,仍然将元素数量减1作为掩码使用。
public void addLast(E e) {
if (e == null)
throw new NullPointerException();
elements[tail] = e;
if ( (tail = (tail + 1) & (elements.length - 1)) == head)
doubleCapacity();
}
判断数组容量不够时,需要进行扩容,判断方式依然是使用位运算来完成的。
对于循环数组中判断是否达到最大容量的一种方法就是判断tail与head是否相等,如果相等,那么首位相交,达到最大容量。
但是,在起始位置时候,head与tail也可能会相等。那么,可以将tail指向下一个元素将要插入的位置,那么当:
(tail + 1) % array.length == head
此时,也就是下一个将要插入的位置与head索引相同,那么证明这个数组已经被填充满了,需要扩容了。
在上面的源代码中,使用了掩码的位运算方式来代替取模的方式,效率更高。可见,数组长度是2的n次幂数值用处很大。
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; //扩容一倍
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;
}