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; }
看上圖,第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; }