Queue繼承自 Collection,咱們先來看看類結構吧,代碼量比較少,我直接貼代碼了前端
public interface Queue<E> extends Collection<E> {
boolean add(E var1);
boolean offer(E var1);
E remove();
E poll();
E element();
E peek();
}複製代碼
從方法名上不太好猜每一個方法的做用,咱們直接來看 API 吧算法
~ | 拋出異常 | 返回特殊值 |
---|---|---|
插入 | add(e) | offer(e) |
移除 | remove() | poll() |
檢查 | element() | peek() |
好像就除了對增刪查操做增長了一個不拋出異常的方法,沒什麼特色吧,咱們繼續看描述~後端
在處理元素前用於保存元素的 collection。除了基本的 Collection 操做外,隊列還提供其餘的插入、提取和檢查操做。每一個方法都存在兩種形式:一種拋出異常(操做失敗時),另外一種返回一個特殊值(null 或 false,具體取決於操做)。插入操做的後一種形式是用於專門爲有容量限制的 Queue 實現設計的;在大多數實現中,插入操做不會失敗。api
就描述了這三組方法的區別,那麼之後我操做隊列儘可能用不拋出異常的方法總行了吧。另外也沒看出什麼名堂,那麼隊列這個接口究竟是規範了什麼行爲?我記得隊列好像是一種數據經常使用的結構,咱們來看看百度百科的定義吧數組
隊列是一種特殊的線性表,特殊之處在於它只容許在表的前端(front)進行刪除操做,而在表的後端(rear)進行插入操做,和棧同樣,隊列是一種操做受限制的線性表。進行插入操做的端稱爲隊尾,進行刪除操做的端稱爲隊頭。安全
看了百度百科的描述,才知道隊列規範了集合只容許在表前端刪除,在表後端插入。這不就是 FIFO 嘛~~bash
FIFO 是英語 first in first out 的縮寫。先進先出,想象一下,在車輛在經過不容許超車的隧道時,是否是先進入隧道的車輛最早出隧道。數據結構
這個問題我回答不了,隊列只是一種數據結構,在某些特定的場合,用隊列實現效率會比較高。併發
AbstractQueue 是Queue 的抽象實現類,和Lst、Set 的抽象實現類同樣,AbstractQueue 也繼承自 AbstractCollection。
AbstractQueue 實現的方法很少,主要就 add、remove、element 三個方法的操做失敗拋出了異常。ide
PriorityQueue 直接繼承自 AbstractQueue,而且除序列號接口外,沒實現任何接口,大概算是最忠誠的 Queue 實現類吧。照慣例,咱們先來看看 API 介紹。
一個基於優先級堆的無界優先級隊列。優先級隊列的元素按照其天然順序進行排序,或者根據構造隊列時提供的 Comparator 進行排序,具體取決於所使用的構造方法。優先級隊列不容許使用 null 元素。依靠天然順序的優先級隊列還不容許插入不可比較的對象.
此隊列的頭 是按指定排序方式肯定的最小 元素。若是多個元素都是最小值,則頭是其中一個元素——選擇方法是任意的。隊列獲取操做 poll、remove、peek 和 element 訪問處於隊列頭的元素。
優先級隊列是無界的,可是有一個內部容量,控制着用於存儲隊列元素的數組大小。它一般至少等於隊列的大小。隨着不斷向優先級隊列添加元素,其容量會自動增長。無需指定容量增長策略的細節。
進隊列的數據還要進行排序,每次取都是取到元素最小值,尼瑪,說好的 FIFO 呢?好吧,我暫且當這是一個取出時有順序的隊列,看起來和昨天學的 TreeSet 功能差很少哈。
PriorityQueue 叫優先隊列,即優先把元素最小值存到隊頭。想象一下,使用PriorityQueue去管理一個班的學生,根據能夠年齡、成績、身高設置好對應的 Comparator ,而後就能自動從小到大排序呢。哈哈哈~
咱們先來看一下 PriorityQueue 的實現吧~
類成員變量以下~
public class PriorityQueue<E> extends AbstractQueue<E> implements Serializable {
private static final long serialVersionUID = -7720805057305804111L;
private static final int DEFAULT_INITIAL_CAPACITY = 11;
transient Object[] queue;
private int size;
private final Comparator<? super E> comparator;
transient int modCount;
private static final int MAX_ARRAY_SIZE = 2147483639;
}複製代碼
沒錯,基於數組的實現,也能找到 grow 擴容方法,少了 List 的各類方法,Queue 的方法咱們前面也看了。那麼咱們就以前去看他是怎麼實現優先隊列的~
思考一下,既然是數組實現,又能按元素大小順序去取出,那麼確定是在添加元素的時候作的排序,直接把對應的元素值大小的元素添加到對應的位置。那麼咱們就從 add 方法看起吧~~
public boolean add(E var1) {
return this.offer(var1);
}
public boolean offer(E var1) {
if(var1 == null) {
throw new NullPointerException();
} else {
++this.modCount;
int var2 = this.size;
if(var2 >= this.queue.length) {
this.grow(var2 + 1);
}
this.size = var2 + 1;
if(var2 == 0) {
this.queue[0] = var1;
} else {
this.siftUp(var2, var1);
}
return true;
}
}
private void siftUp(int childIndex) {
E target = elements[childIndex];
int parentIndex;
while (childIndex > 0) {
parentIndex = (childIndex - 1) / 2;
E parent = elements[parentIndex];
if (compare(parent, target) <= 0) {
break;
}
elements[childIndex] = parent;
childIndex = parentIndex;
}
elements[childIndex] = target;
}複製代碼
上面的方法調用都很簡單,我就不寫註釋了,add 調用 offer 添加元素,若是集合裏面的元素個數不爲零,則調用 siftUp 方法把元素插入合適的位置。
敲黑板~~接下來的東西我看了老半天才看明白。有點吃力
注意了,siftUp裏面的算法有點奇怪,我一開始還覺得是二分插入法,然而並非。
首先,咱們這裏走進了一個誤區,PriorityQueue 雖然是一個優先隊列,可以知足咱們剛剛說的需求,把一個班的學生按年齡大小順序取出來,可是在內存中(數組中)的保存卻並非按照從小到大的順序保存的,可是一直 poll,是可以按照元素從小到大的順去取出結果。
這裏我作了一個小測試。
PriorityQueue<Integer> integers = new PriorityQueue<>();
integers.add(8);
integers.add(6);
integers.add(5); 複製代碼
已知 PriorityQueue 用數組存儲,你們猜猜我這樣存進隊列的三個數子是怎樣存儲的?
一開始我覺得是五、六、8的順序,可是 debug 的時候看到 PriorityQueue 裏面保存數據數組裏面的存放順序是五、八、6.why?
而後我調用下面這個方法打印~
while (!integers.isEmpty()) {
Log.e("_____", integers.poll() + "~~");
} 複製代碼
結果是五、六、8.這他媽就尷尬了。
而後怎麼辦~去找度娘唄。。。
好了,開始解析~~
不知道你們記不記得一種數據結構叫二叉樹,這裏就是使用了二叉樹的思路,因此比較難理解。
首先,這裏使用的是一種特殊的二叉樹:1.父節點永遠小於子節點,2.優先填滿第 n 層樹枝再填 n+1 層樹枝。也就是說,數組裏面的五、八、6是這樣存儲的
依次添加元素八、六、5.
5
/ \
8 6
‖
∨
數組角標位置
0
/ \
1 2複製代碼
這樣能理解了吧,再回過頭去看siftUp方法,捋一下添加元素的過程。
添加8
沒什麼好說的,直接添加一個元素到到數組[0]便可,二叉樹添加一個頂級節點
添加5
首先把[1]的位置賦值給5,使得數組中的元素爲{8,5}
而後執行siftUp(1)方法(1是剛剛插入元素5的角標)
siftUp方法首先獲取5的父節點,判斷5是否小於父節點。
若是小於,則交換位置繼續比較祖父節點
若是大於或者已經到頂級節點,結束。複製代碼
siftUp方法後,數組變爲{5,8}
添加6
重複上面的動做,數組變爲{5,8,6}
問:若是此時添加數字7,數組的順序是多少?
思考一下3分鐘~~
好,3分鐘過去了,結果是{5,7,6,8}
爲何會這樣?拿着數字7代入到上面的方法中去算呀,首先8在數組中的角標是3,3要去和父節點比,求父節點的公式是(3-1)/2 = 1.因而父節點的角標是1,7<8,所以交換位置,此時角標1還有父節點 (1-1)/2 = 0,再比較7和5,7>5,知足大於父節點條件,結束。
好了,如今應該明白了吧~~~沒明白再回過頭去理解一遍。
接下來,咱們來看循環調用 poll() 方法是怎樣從{5,8,6}的數組中按照從小到大的順序取出五、六、8.
咱們來看 poll()方法
public E poll() {
if (isEmpty()) {
return null;
}
E result = elements[0];
removeAt(0);
return result;
}
private void removeAt(int index) {
size--;
E moved = elements[size];
elements[index] = moved;
siftDown(index);
elements[size] = null;
if (moved == elements[index]) {
siftUp(index);
}
}
private void siftDown(int rootIndex) {
E target = elements[rootIndex];
int childIndex;
while ((childIndex = rootIndex * 2 + 1) < size) {
if (childIndex + 1 < size
&& compare(elements[childIndex + 1], elements[childIndex]) < 0) {
childIndex++;
}
if (compare(target, elements[childIndex]) <= 0) {
break;
}
elements[rootIndex] = elements[childIndex];
rootIndex = childIndex;
}
elements[rootIndex] = target;
}複製代碼
這是 api23 裏面 PriorityQueue 的方法,和 Java8 略有不一樣,但實現都是同樣的,只是方法看起來好理解一些。
首先 poll 方法取出了數組角標0的值,這點不用質疑,由於角標0對應二叉樹的最高節點,也就是最小值。
而後在 removeAt 方法裏面把數組的最後一個元素覆蓋了第0個元素,再是將最後一個元素置空,好,到了這裏,進入第二個關鍵點了,黑板敲起來~~
這裏在賦值以後調用了 siftDown(0);
咱們來看 siftDown()方法~
這個方法從0角標(最頂級父節點)開始,先判斷左右子節點,取較小的那個一,和父節點比較,而後再對比左右子節點。根據咱們這裏二叉樹的特色,最終能取到最小的那個元素放到頂級父節點,保證下一次 poll能取到當前集合最小的元素。具體代碼不帶着讀了~~
ok,PriorityQueue 看完了。
#Deque
剛剛咱們一直在找 FIFO 的集合,找到個 PriorityQueue,然而並非。
而後咱們繼續找唄,發現了 Queue 有一個子接口Deque
來看看 API 文檔的定義~
一個線性 collection,支持在兩端插入和移除元素。名稱 deque 是「double ended queue(雙端隊列)」的縮寫,一般讀爲「deck」。大多數 Deque 實現對於它們可以包含的元素數沒有固定限制,但此接口既支持有容量限制的雙端隊列,也支持沒有固定大小限制的雙端隊列。
此接口定義在雙端隊列兩端訪問元素的方法。提供插入、移除和檢查元素的方法。每種方法都存在兩種形式:一種形式在操做失敗時拋出異常,另外一種形式返回一個特殊值(null 或 false,具體取決於操做)。插入操做的後一種形式是專爲使用有容量限制的 Deque 實現設計的;在大多數實現中,插入操做不能失敗。
嗯~就是一個首尾插入刪除操做都直接的接口。
咱們剛剛說了 Queue 遵循 FIFO 規則,當有了 Deque,咱們還能實現 LIFO(後進先出)。反正像先進後出、後進先出都能在 Deque 的實現類上作到,具體看各位 Coder 們怎麼操做了。
總結一下 Deque 的方法~
~~-- | __第一個元素(頭部)..... | _最後一個元素(尾部) |
---|---|---|
~ | 拋出異常 | 特殊值 | 拋出異常 | 特殊值 |
---|---|---|---|---|
插入 | addFirst(e) | offerFirst(3) | addLast(e) | offerLast(3) |
移除 | removeFirst() | pollFirst() | removeLast() | pollLast() |
檢查 | getFirst() | peekFirst() | getLast() | peekLast() |
__特麼的,MD 語法不支持這種不對齊表格
若是想用做 LIFO 隊列,應優先使用此接口,而不是遺留的 Stack 類。在將雙端隊列用做堆棧時,元素被推入雙端隊列的開頭並從雙端隊列開頭彈出。堆棧方法徹底等效於 Deque 方法,以下表所示:
堆棧方法 | 等效 Deque 方法 |
---|---|
push(e) | addFirst(e) |
pop() | removeFirst() |
peek() | peekFirst() |
就醬紫吧,也沒什麼特別的,我我的不太喜歡這個接口,我以爲這個接口規範的行爲有點多,不符合接口隔離原則和單一職能原則。
接下來咱們就去看看 Deque 的實現類吧。
看兩個具備表明性的類吧,第一個是基於數組實現的 ArrayQeque,第二個是基於鏈表實現的LinkedList。
前面 List 的時候咱們看過 LinkedList,LinkedList 繼承自AbstractList,同時也實現了 List 接口,所以這是一個很全能的類。一句話描述就是:基於鏈表結構實現的數組,同時又支持雙向隊列操做。
還記得以前在 List 結尾留的一個思考題麼:怎樣用鏈表的結構快速實現棧功能LinkedListStack?
public class LinkedListStack extends LinkedList{
public LinkedListStack(){
super();
}
@Override
public void push(Object o) {
super.push(o);
}
@Override
public Object pop() {
return super.pop();
}
@Override
public Object peek() {
return super.peek();
}
@Override
public boolean isEmpty() {
return super.isEmpty();
}
public int search(Object o){
return indexOf(o);
}
}複製代碼
吶,這裏給出了實現,其實什麼都沒作,就是調用了父類方法。這個類只是看起來結構清晰的實現了 LIFO,可是因爲繼承自 LinkedList,仍是能夠調用 addFirst 等各類「非法操做方法」,這就是我說的不理解 Java 爲何要這樣設計,還推薦使用 Deque 替換棧實現。項目實際開發中,同窗們要使用棧結構直接用 LinkedList就好了,我這裏 LinkedListStack 只是便於你們理解 LinkedList 也能夠用做棧集合。
照慣例先看 API 定義~
Deque接口的大小可變數組的實現。數組雙端隊列沒有容量限制;它們可根據須要增長以支持使用。它們不是線程安全的;在沒有外部同步時,它們不支持多個線程的併發訪問。禁止 null 元素。此類極可能在用做堆棧時快於 Stack,在用做隊列時快於 LinkedList。
感受 ArrayDeque 纔是一個正常的 Deque 實現類,ArrayDeque 直接繼承自 AbstractCollection,實現了Deque接口。
類部實現和 ArrayList 同樣都是基於數組,當頭尾下標相等時,調用doubleCapacity()方法,執行翻倍擴容操做。
頭尾操做是什麼鬼?咱們都知道ArrayDeque 是雙向列表,就是能夠兩端一塊兒操做的列表。所以使用了兩個指針 head 和tail 來保存當前頭尾的 index,一開始默認都是0角標,當添加一個到尾的時候,tail先加1,再把值存放到 tail 角標的數組裏面去。
那麼 addFirst 是怎麼操做的呢?head 是0,添加到-1的角標上面去?其實不是的,這裏 你能夠把這個數組當成是一個首尾相連的鏈表,head 是0的時候 addFirst 其實是把值存到了數組最後一個角標裏面去了。即: 當 head 等於0的時候 head - 1 的值 數組.length - 1,代碼實現以下。
如圖,這是我以下代碼的執行添加60時 debug
ArrayDeque<Integer> integers = new ArrayDeque<>();
integers.addLast(8);
integers.addFirst(60);複製代碼
而後當head == tail的時候表示數組用滿了,須要擴容,就執行doubleCapacity擴容,這裏的擴容和 ArrayList 的代碼差很少,就不去分析了。
凡是牽涉到須要使用 FIFO 或者 LIFO 的數據結構時,推薦使用 ArrayDeque,LinkedList 也行,還有 get(index)方法~~