ArrayDeque

ArrayDeque簡介

JDK源碼文檔描述特點如下:

  • 動態擴展的循環數組實現的雙端隊列
  • 線程不安全
  • 不允許Null元素
  • 用於棧比Stack快,用於隊列比LinkedList快
  • 大部分操作均攤時間複雜度爲O(1),除了remove, removeFirstOccurrence, removeLastOccurrence, contains, iterator.remove(), and the bulk operations, 複雜度爲O(n)

作爲雙端隊列使用:

Deque<Integer> deque=new ArrayDeque<>();
deque.offerLast(1);//尾部加入
deque.offerFirst(2);//頭部加入
deque.size();//隊列大小
deque.peekFirst();//檢查隊頭
deque.peekLast();//檢查隊尾
deque.pollFirst();//隊頭出隊
deque.pollLast();//隊尾出隊

作爲普通隊列使用:

Queue<Integer> queue=new ArrayDeque<>();
queue.offer(1);//尾部加入
queue.size();//隊列大小
queue.peek();//檢查隊頭
queue.poll();//隊頭出隊

作爲棧使用:

ArrayDeque<Integer> stack=new ArrayDeque<>();//JDK中沒有stack接口
stack.push(1);//入棧
stack.size();//棧深度
stack.peek();//檢查棧頂
stack.poll();//出棧

ArrayDeque實現原理

ArrayDeque內部是一個可擴容的循環數組。循環數組使用頭尾兩個指針,使得物理上的普通數組變爲邏輯上循環的數組。

ArrayDeque中用以下三個實例變量表示:

transient Object[] elements;
transient int head;
transient int tail;

循環數組的特點:

  1. 如果head=tail:數組爲空,長度爲0;
    在這裏插入圖片描述
  2. 如果head<tail:第一個元素爲elements[head],最後一個元素爲elements[tail-1],長度爲tail-head
    在這裏插入圖片描述
  3. 如果head>tail,且tail=0:第一個元素爲elements[head],最後一個元素爲elements[elements.length-1],長度爲elements.length-head
    在這裏插入圖片描述
  4. 如果head>tail>0:形成循環。第一個元素爲elements[head],最後一個元素爲elements[tail-1],元素索引從head到elements.length-1,再從0到tail-1
    在這裏插入圖片描述

構造方法

默認構造函數,會創建長度爲16的數組:

public ArrayDeque() {
   elements = new Object[16];
}

帶初始容量的構造函數,會計算最合適的數組長度:

public ArrayDeque(int numElements) {
    allocateElements(numElements);
}

private void allocateElements(int numElements) {
    elements = new Object[calculateSize(numElements)];
}

爲了使用與運算代替求餘,會保證長度爲2的n次冪。calculateSize方法的核心就是計算嚴格大於numElements的2的n次冪數。注意是“嚴格大於”,因爲循環數組必須至少有一個空位置。

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;
}

尾部添加

從尾部添加,核心是找到添加位置,用與運算最方便:(tail + 1) & (elements.length - 1))

public void addLast(E e) {
    if (e == null)
        throw new NullPointerException();
    elements[tail] = e;
    if ( (tail = (tail + 1) & (elements.length - 1)) == head)
        doubleCapacity();
}

如果尾部添加的位置和head重合了,表示數組要滿了,必須擴容。擴容大小很簡單,兩倍。關鍵是如何“整理元素”,把元素移到新數組的左端。

private void doubleCapacity() {
    assert head == tail;//擴容前置條件一定是這個
    int p = head;
    int n = elements.length;
    int r = n - p; // head右邊元素(含head本身)的個數,這些元素要先移到新數組左邊
    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);//先複製 head右邊元素(含head本身)
    System.arraycopy(elements, 0, a, r, p);//再複製head左邊元素
    elements = a;
    head = 0;
    tail = n;
}

頭部添加

關鍵也是添加的位置:(head - 1) & (elements.length - 1)

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 = (h + 1) & (elements.length - 1)。還有一點值得注意,elements[h] = null,把頭指針位置置空,避免內存泄漏。

public E pollFirst() {
    int h = head;
    @SuppressWarnings("unchecked")
    E result = (E) 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;
}

尾部刪除

尾部刪除也類似,元素位置是:(tail - 1) & (elements.length - 1)。

    public E pollLast() {
        int t = (tail - 1) & (elements.length - 1);
        @SuppressWarnings("unchecked")
        E result = (E) elements[t];
        if (result == null)
            return null;
        elements[t] = null;
        tail = t;
        return result;
    }

隊列長度

循環數組長度計算公式:(tail - head) & (elements.length - 1)

public int size() {
    return (tail - head) & (elements.length - 1);
}
發佈了31 篇原創文章 · 獲贊 13 · 訪問量 2萬+
發表評論
所有評論
還沒有人評論,想成為第一個評論的人麼? 請在上方評論欄輸入並且點擊發布.
相關文章