了解特性,先看下体系结构:
如上所看到的,知道其支持 序列化,克隆,迭代器操做,队列特性。详细实现 除了实现以上接口外,扩展AbstractCollection 抽象类。html
大炮打苍蝇。仍是鸟枪打野猪?工具应用要有场景:
ArrayDeque 为双端队列,支持首部,尾部两端的操做,所以作双端操做可用于fifo等queue, 作单端操做可作为stack.
然而能作queue的还有linkedList, vector等,能作stack有Stack,还要ArrayDeque干吗?
仍是那句话,“应用”是要讲究“场景”的:
linkedList内部实现用node节点连接先后元素。模拟c/c++的链表(长处在于中间节点的增删操做为o(1))。
vector方法加着synchronized修饰(同步 将带来性能的损耗)。Stack的实现又继承自vector,问题同上。
ArrayDeque 的底层实现为单纯的数组操做。因此单从性能上看。ArrayDeque在优于他们。固然由于没有作同步处理,因此存在并发问题。需要调用方本身保障。java
源代码实现部分。看上去仍是相对亲切的(当年大学书本上背烂的链表,堆,队列实现),又复习了一遍C语言入门的队列实现。node
固然jdk中的设计毕竟是巧妙的。
挑几处分析下:linux
public boolean isEmpty() {
return head == tail;
}
public int size() {
return (tail - head) & (elements.length - 1);
}
public E pollFirst() {
int h = head;
E result = elements[h]; // Element is null if deque empty
if (result == null)
return null;
elements[h] = null; // Must null out slot
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;
}
先说明下关键变量 意义:
head 第一个元素的索引
tail 最后一个元素以后的索引。
如 head指向0。tail指向3。那么有效元素为索引0,1,2 指向的元素,因此元素个数 size() 为 3(即3-0)。c++
看下上述实现奇怪的地方,大量如此的操做,为什么?算法
(tail - head) & (elements.length - 1)
(h + 1) & (elements.length - 1)
缘由在于 arraydeque在实现双端队列时才用为循环数组。存在 tail < head的状况,因此要经过 & (elements.length - 1)操做来修正,使其为正值。数组
因此咱们会想到经过补码是实现是可以的,然而单凭 &(elements.length - 1) 就实现修正,仍是难以让咱们信服的。markdown
若是 tail = 3, head = 6, elements.length=10, 那么结果也应该是 7啊 (元素的索引为6,7。8,9。0,1,2),而非 -3&10 = 8 啊 (-3 补码 1111 1101 10补码 0000 1010 结果 0000 1000 为8)
不思不得其解,再看源代码,当中有一句话
/**
* The minimum capacity that we’ll use for a newly created deque.
* Must be a power of 2.
*/
因此,一切都明确了,elements.length 即数组长度是有要求的,必须是2的幂指数,如此一切可解。測试一下elments.length 为16,32等都顺利成章经过了。原理也很是easy,elments.length 二进制位 00100..00,elments.length-1 为 000111…11可以作掩码了。如此,上述源代码理解经过。并发
关于arrayDequeue在分配空间,要求必须是 2的幂指数。当中有一段实现:工具
private void allocateElements(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
}
elements = (E[]) new Object[initialCapacity];
}
关于此处算法的出处。我的眼下尚未理解,哪位大侠知道还请告知。但是可否work,咱们仍是要验证一下,一下借用一下他人的方法:
public class ArrayDequeCapacity {
public static void main(String[] args) {
for (int i = 1; i < 32; i++) {
int n = (int) Math.pow(2, i) - 1;
System.out.println(i + " " + n + " " + getCapacity(n));
}
}
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;
}
}
结果:
1 1 2 2 3 4 3 7 8 4 15 16 5 31 32 6 63 64 7 127 128 8 255 256 9 511 512 10 1023 1024 11 2047 2048 12 4095 4096 13 8191 8192 14 16383 16384 15 32767 32768 16 65535 65536 17 131071 131072 18 262143 262144 19 524287 524288 20 1048575 1048576 21 2097151 2097152 22 4194303 4194304 23 8388607 8388608 24 16777215 16777216 25 33554431 33554432 26 67108863 67108864 27 134217727 134217728 28 268435455 268435456 29 536870911 536870912 30 1073741823 1073741824 31 2147483646 1073741824
从验证结果来看,对于计算大于等于一个数的最小2的幂指数运算,这种方法仍是工做的很是好的。
那么。问题又来了。为何分配空间必定要按2的幂指数。我是赞同其它同窗观点的(有其它理由的还请补充):
linux内存分配管理中的伙伴系统以连续2的幂指数(1。2,4,8,…1024)个页帧(page frame) 为单位进行空间分配,而页帧作为标准内存分配单元大小4K, 因此才用 2的幂指数为大数组分配空间是有利于进行内存管理的。
如上式:
public int size() {
return (tail - head) & (elements.length - 1);
}
若是没有对 elements.length作要求的话,就要例如如下推断来
int sub = tail - head;
if(sub >= 0) return sub;
else {
return sub + elements.length;
}
关于arrayDequeue的应用和实现。大体如此。