Queue的傳承

1.  Queue傳承後代介紹java

Queue:  Deque :  ArrayDeque數組

                              LinkedList數據結構

               AbstractQueue :  PriorityQueue性能

注:橙色爲繼承,黑色爲實現。測試

Queue隊列,最明顯的特徵是:先進先出。該接口提供了6個接口,分爲三組:spa

(1) add 和 offer : 隊尾添加元素code

(2) remove 和 poll: 移除 隊列的頭對象

(3) element 和 peek: 獲取隊列的頭,但不移除繼承

很明顯,這三對的add,remove,element繼承Collection的, 後面三個纔是Queue新增的接口。隊列的三個接口當遇到超出邊界時,不會拋出異常,要麼返回false,要麼返回null;而Collection的三個接口會拋出異常。索引

2.  Deque接口詳情

Deque打破了隊列的限制,能夠在隊尾隊頭實現元素的添加、移除、獲取。

(1) addFirst / offerFirst  和 addLast / offerLast: 添加元素

(2) removeFirst / pollFirst 和 removeLast / pollLast: 移除元素

(3) getFirst / peekFirst 和 getLast / peekLast: 獲取隊列的頭,但不移除

3.  ArrayDeque具體實現

要弄清楚ArrayDeque具體實現,則只需研究它的存儲結構和元素存儲方式索引便可。下面是關鍵變量:

public class ArrayDeque<E> extends AbstractCollection<E>
                           implements Deque<E>, Cloneable, Serializable
{

    transient Object[] elements; // non-private to simplify nested class access

    transient int head;          

    transient int tail;

    // ...

}

由上面源碼可知,這裏的存儲結構是數組。說實話很容易想固然,一個數組末端是隊頭,開端是隊尾,正常隊列添加元素的位置是隊尾,也就是說數組,每添加一個元素,全隊向後移1位;而後考慮隊頭出隊列...每添加新元素,隊頭索引+1,出隊列減1....每添加1個元素還需從新拷貝到新數組...等等,仔細想來這也太不靠譜了吧。其實,咱們無需關注隊列元素實際是怎麼存儲的,只需保證出隊列的順序正確就能夠;換一句話說,數組裏面無需保證隊尾和對頭的位置在兩端,他們甚至能夠在任何一位置。

ArrayDeque的添加以及索引方式:

(1) 熟悉一下位運算

// 00000001 & 00000010 = 0
System.out.println( 1 & 2);

// 11111111 & 00000010 = 2
System.out.println( -1 & 2)

// 結論: 當 y>=0 時, 有 x & y <= y 成立

在這裏咱們注意到: 利用 & 運算能夠進行邊界控制,數組無需進行邊界檢查,提升性能(一樣能夠用於集合)。

(2) 添加元素

public void addFirst(E e) {   // 在隊頭上添加元素
    if (e == null)
        throw new NullPointerException();
    elements[head = (head - 1) & (elements.length - 1)] = e;
        if (head == tail)
            doubleCapacity();
}


public void addLast(E e) {    // 在隊尾上添加元素
    if (e == null)
        throw new NullPointerException();
    elements[tail] = e;
        if ( (tail = (tail + 1) & (elements.length - 1)) == head)
            doubleCapacity();
}

首先測試一下:

String[] array = new String[4];  //數組
int h = 0;    // head
int t = 0;    // tail

h = ((h - 1) & (array.length - 1));            // h = 3
h = ((h - 1) & (array.length - 1));            // h = 2
h = ((h - 1) & (array.length - 1));            // h = 1
h = ((h - 1) & (array.length - 1));            // h = 0

h = ((h - 1) & (array.length - 1));            // h = 3
h = ((h - 1) & (array.length - 1));            // h = 2


t = ((t + 1) & (array.length - 1));            // t = 1
t = ((t + 1) & (array.length - 1));            // t = 2
t = ((t + 1) & (array.length - 1));            // t = 3

t = ((t + 1) & (array.length - 1));            // t = 0
t = ((t + 1) & (array.length - 1));            // t = 1
t = ((t + 1) & (array.length - 1));            // t = 2

進一步證實上面的結論。

(3) 當 t = h時,隊列滿了,則能夠擴容了:

private void doubleCapacity() {
    assert head == tail;
    int p = head;
    int n = elements.length;
    int r = n - p;                // 頭下標索引到數組末端的元素個數 
    int newCapacity = n << 1;     // 左移,擴大兩倍
    if (newCapacity < 0)
        throw new IllegalStateException("Sorry, deque too big");
    Object[] a = new Object[newCapacity];

    // Object src, int srcPos, Object dest, int destPos, int length
    System.arraycopy(elements, p, a, 0, r);  // 舊數組中頭右邊的元素(包括頭)拷貝到從下標0開始的新數組中
    System.arraycopy(elements, 0, a, r, p);  // 舊數組開始到頭前面的元素拷貝到從r下標開始的新數組中

    elements = a;                     

    head = 0;     // 頭下標變爲0      這和剛開始添加元素的head是同樣的,下一次隊頭的位置是 新數組末端
    tail = n;     // 尾下標變爲舊數組的長度    
}

(4) 存儲特色(出隊列)

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);   // 頭索引變化,添加時是減1,移除天然是加1
    return result;
}

java_queue_001

看上圖,第1次在隊頭添加元素,索引爲10;第2次在隊頭添加元素,索引爲9,可見隊頭的位置並非在數組的末端,而是一直在變。同理,隊尾也是如此。咱們只需記住位置索引,保證出隊列的順序就能夠了(這一點經過上面&運算實現)。

4. PriorityQueue隊列

PriorityQueue 裏面存放的元素不能爲null,且要求是能做比較的元素(即實現了 Comparator 接口);最小的元素在隊頭,能夠存在相等的元素;固然因此該類的構造方法中,能夠傳遞一個Comparator進去。它的數據結構叫堆(雖然容器是數組),在這裏是最小堆,也就是堆的最頂端是最小的。

該堆實現一個二叉樹,這棵二叉樹的根節點是queue[0],左子節點是queue[1],右子節點是queue[2],而queue[3]又是queue[1]的左子節點,依此類推,給定元素queue[i],該節點的父節點是queue[(i-1)/2] 。因此給定位置k,它的父節點是:(k-1)/2,即(k-1)<<<2。

public boolean offer(E e) {
    if (e == null)
        throw new NullPointerException();
    modCount++;
    int i = size;               // 添加新元素的節點老是在數組的末端
    if (i >= queue.length)      // 擴容
        grow(i + 1);
    size = i + 1;
    if (i == 0)                 // 隊列插入第一個元素的節點,無需比較大小
        queue[0] = e;
    else
        siftUp(i, e);           // 調整堆  拿父節點和新插入元素節點比較,若父節點值大於新元素,則交換。至關於提高位置
    return true;
}

/**
 * @param k the position to fill
 * @param x the item to insert
 */
private void siftUp(int k, E x) {
    if (comparator != null)
        siftUpUsingComparator(k, x);    // 使用構造傳遞進來的Comparator對象
    else
        siftUpComparable(k, x);         // 使用元素對象實現的Comparator比較方法
}

private void siftUpUsingComparator(int k, E x) {
    while (k > 0) {
        int parent = (k - 1) >>> 1;     // 無符號右移, 拿到即將插入元素節點對於的父節點位置
        Object e = queue[parent];
        if (comparator.compare(x, (E) e) >= 0)   // 若是新元素節點的值大於父節點的,則無需調整,直接賦值
            break;
        queue[k] = e;                   // 不然,新元素節點和父節點交換位置
        k = parent;
    }
    queue[k] = x;
}
相關文章
相關標籤/搜索