java ArrayDeque源碼圖析

    事情是這樣的

    在一次開發當中,我想用到隊列的相關特性,這次隊列不用考慮在併發情況下的安全特性,併發包裏面的數據結構就不用考慮了。於是我找啊找,到了jdk中隊列的實現有LinkedList,ArrayDeque,PriorityQueue。因爲這次使用不需要考慮優先級,首先就排除了PriorityQueue。剩下LinkedList,ArrayDeque了。很糾結啊,到底用誰呢,LinkdList很熟悉,是一個雙向鏈表,對於頭部和尾部的增刪操作支持都非常好,再看看ArrayDeque,他認識我,我不是很認識它,我就帶着好奇的心態去詳細的瞭解這個結構。

    看到ArrayDeque 描述說,如果你想把LinkedList當作隊列來用,那麼ArrayDeque會更快。哈哈,正合我意,爲什麼這麼快呢?讓我們來詳細瞭解這個結構吧。首先ArrayDeque 是一個用數組實現的沒有容量限制的雙端隊列。所謂雙端隊列,是可以在隊列的頭部和尾部都進行進行如添加和刪除操作的隊列。ArrayDeque繼承了AbstractCollection,實現了Deque。ArrayDeque的增刪操作都是圍繞head下標和tail下標來對數組進行操作的。對一塊連續的內存進行讀取,設置某內存的值都是很快的。

優缺點:

1.沒有容量限制。

2.多線程環境下不支持併發訪問。

3.不支持插入空元素。

4.當把LinkedList 用做queue 的時候,把Stack 用做stack 時,ArrayDeque 速度會比他們更快。

到底有多快,數據說話

當我們分別用ArrayDeque和LinkedList 分別對1百萬個數進行入隊,出隊的操作時,他們所花費的時間:

 

package java_util_t;
import java.util.ArrayDeque;
import java.util.LinkedList;
import java.util.Queue;

public class ArrayDequeDemo {
    
    /**
     * @param args
     */
    public static void main(String[] args) {
        int time = 1000000;
        
        long s1 = System.currentTimeMillis();
        Queue<Integer> queue1 = new ArrayDeque<Integer>(time);
        for (int i = 0; i < time; i++) {
            queue1.offer(i);
        }
        for (int i = 0; i < time; i++) {
            queue1.poll();
        }
        System.out.println("ArrayDeque cost time:" + (System.currentTimeMillis() - s1));
        
        long s2 = System.currentTimeMillis();
        Queue<Integer> queue2 = new LinkedList<Integer>();
        for (int i = 0; i < time; i++) {
            queue2.offer(i);
        }
        for (int i = 0; i < time; i++) {
            queue2.poll();
        }
        System.out.println("LinkedList cost time:" + (System.currentTimeMillis() - s2));
    }

}



 

 

 

  源碼分析

對於ArrayDeque源碼的分析主要從Queue接口的實現來分析,就是說一端入隊一端出隊的結構來分析。

1.ArrayDeque 的構造

 當我們我們初始化一個Queue<Integer> deque = new ArrayDeque<Integer>(6); 時候。會默認選一個>6的最小的2^n。

 

    public ArrayDeque() {//底層默認大小爲16 的Object 數組
        elements = (E[]) new Object[16];
    }
    

    public ArrayDeque(int numElements) {//構造一個 容量>= numElients 的隊列 
        allocateElements(numElements);//分配底層數組元素,默認爲null
    }

    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) {//這裏設計很巧妙,會尋找一個>numElements 的2的n次冪的一個初始容量,如果數值越界了導致出現了負數,就給個2^30
            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];
    }

 

2.ArrayDeque 入隊操作

 

    Queue<Integer> deque = new ArrayDeque<Integer>(6);
    deque.offer(1);

    當我們執行這個操作的時候:

    

 

public boolean offer(E e) {//隊列入隊操作
    return offerLast(e);//隊列尾部入隊操作
}
public boolean offerLast(E e) {
    addLast(e);
    return true;
}
public void addLast(E e) {
    if (e == null)//不允許插入Null元素,否則拋出異常
        throw new NullPointerException();
    elements[tail] = e;//設置tail下標元素
    if ( (tail = (tail + 1) & (elements.length - 1)) == head)//你可以把數組隊列想象成一個環形數組,當tail+1超過數組最後一個下標時,&操作返回0,此時tail ==head
 doubleCapacity();//擴容
}

private void doubleCapacity() { //入隊時,如果隊列元素爲空,就會擴容
    assert head == tail; 
    int p = head; 
    int n = elements.length; 
    int r = n - p; 
    int newCapacity = n << 1;//構造或者上一次擴容時隊列的容量總是爲2^n,當擴充容量爲當前容量的2倍時,只需一個左移操作即可。
    if (newCapacity < 0) throw new IllegalStateException("Sorry, deque too big"); 
    Object[] a = new Object[newCapacity];
    System.arraycopy(elements, p, a, 0, r);//把舊隊列head下標到(element.length-1下標)的元素複製到新隊列的開頭。(1)部分
    System.arraycopy(elements, 0, a, r, p);//複製0~head下標之間的元素到到新隊列的(1)部分之後。
    elements = (E[])a; //替換舊的元素數組
    head = 0;//重新設置頭結點 
    tail = n;//重新設置尾節點
}

擴容圖示:

當我們執行以下代碼時,會發生擴容

 

	Queue<Integer> deque = new ArrayDeque<Integer>(6);
		for (int i = 1; i <= 7; i++) {
			deque.offer(i);
		}	
		//第(1)部分
		deque.offer(8);//第(2)部分,當執行這串代碼的時候會擴容


當程序跑到第(1)部分時,已經入隊了7個元素此時,

 

繼續入隊8會發生擴容:

 

deque.offer(8);此時這個操作會判斷tail == head 將當前容量設置爲雙倍容量

 

3.ArrayDeque 出隊操作

我們執行如下操作

Queue<Integer> deque = new ArrayDeque<Integer>(6);
        deque.offer(1);
        deque.poll();

當出隊的時候:

 

public E poll() {//隊列出隊
    return pollFirst();//出隊第一個元素
}
public E pollFirst() {
    int h = head;
    E result = elements[h]; //出隊操作時,會返回第一個元素給調用者
    if (result == null)
        return null;
    elements[h] = null;     //出隊操作的實質是通過將隊列的頭元素設置爲null,然後再將head下標往後移動一位。
    head = (h + 1) & (elements.length - 1);//這裏就是上一步說的將head下標往後移動一位,
    //而這個head = (h + 1) & (elements.length - 1) 操作保證head在往後移動的時候不會數組越界
    return result;
}

 

4.ArrayDeque的一些訪問操作

 

 

 

public E element() {//查看隊列頭元素,其實跟peek()唯一不同的是,當隊列爲空時,element()會拋出NoSuchElementException
    return getFirst();//獲取頭元素
}
public E getFirst() {
    E x = elements[head];//直接返回數組head下標指向的元素
    if (x == null)
        throw new NoSuchElementException();
    return x;
}
 public E peek() {//查看隊列頭元素,當隊列爲空時直接返回null,不拋異常
       return peekFirst();
 }
  public E peekFirst() {
        return elements[head]; // 如果隊列爲空時,返回elments[head] = null;
  }

 

 

 

 

5.ArrayDeque 其他的一些操作

 

public void clear() {//清空操作
    int h = head;
    int t = tail;
    if (h != t) { // head != tail 表示隊列元素 不爲空
        head = tail = 0;//設置head 和 tail 初始狀態
        int i = h;
        int mask = elements.length - 1;
        do {
            elements[i] = null;//配合循環將所有元素設置爲null
            i = (i + 1) & mask;
        } while (i != t);
    }
}
public boolean contains(Object o) {//判斷隊列是否包含該元素
    if (o == null)
        return false;
    int mask = elements.length - 1;
    int i = head;
    E x;
    while ( (x = elements[i]) != null) {//從head元素向後豬哥判斷,是否equals
        if (o.equals(x))
            return true;
        i = (i + 1) & mask;
    }
    return false;
}
  public int size() {//獲取隊列元素個數,(tail - head) & (elements.length - 1)保證大小在有效範圍內。
        return (tail - head) & (elements.length - 1);
    }

  public boolean isEmpty() {//入隊操作,tail+=1;出隊操作head+=1;當一直出隊元素的時候,head一直+,會==tail,此時head==tail都指向null元素。
        return head == tail;
    }

 

 

 

 

 

 

 

 

 

 


 

 

 

 

 

 

 

 

 

 

 

發表評論
所有評論
還沒有人評論,想成為第一個評論的人麼? 請在上方評論欄輸入並且點擊發布.
相關文章