ArrayDeque源代碼分析

1. 體系結構

瞭解特性,先看下體系結構:
這裏寫圖片描寫敘述
如上所看到的,知道其支持 序列化,克隆,迭代器操做,隊列特性。詳細實現 除了實現以上接口外,擴展AbstractCollection 抽象類。html

2. 應用場景

大炮打蒼蠅。仍是鳥槍打野豬?工具應用要有場景:
ArrayDeque 爲雙端隊列,支持首部,尾部兩端的操做,所以作雙端操做可用於fifo等queue, 作單端操做可作爲stack.
然而能作queue的還有linkedList, vector等,能作stack有Stack,還要ArrayDeque幹嗎?
仍是那句話,「應用」是要講究「場景」的:
linkedList內部實現用node節點連接先後元素。模擬c/c++的鏈表(長處在於中間節點的增刪操做爲o(1))。
vector方法加着synchronized修飾(同步 將帶來性能的損耗)。Stack的實現又繼承自vector,問題同上。
ArrayDeque 的底層實現爲單純的數組操做。因此單從性能上看。ArrayDeque在優於他們。固然由於沒有作同步處理,因此存在併發問題。需要調用方本身保障。java

3. 源代碼實現

源代碼實現部分。看上去仍是相對親切的(當年大學書本上背爛的鏈表,堆,隊列實現),又複習了一遍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的冪指數運算,這種方法仍是工做的很是好的。

4. 一些問題

那麼。問題又來了。爲何分配空間必定要按2的冪指數。我是贊同其它同窗觀點的(有其它理由的還請補充):

1. 從內存空間分配的角度來說,2的冪指數。更加快內存分配

linux內存分配管理中的夥伴系統以連續2的冪指數(1。2,4,8,…1024)個頁幀(page frame) 爲單位進行空間分配,而頁幀作爲標準內存分配單元大小4K, 因此才用 2的冪指數爲大數組分配空間是有利於進行內存管理的。

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的應用和實現。大體如此。

相關文章
相關標籤/搜索