資料結構之「佇列」
什麼是佇列?
佇列(queue)是隻允許在一端進行插入操作,而在另一端進行刪除操作的線性表。是一種先進先出(First In First Out)的線性表,簡稱 FIFO。允許插入的一端稱為隊尾,允許刪除的一端稱為隊頭。
佇列有 2 種方式來儲存:陣列 和 連結串列。
陣列我們都知道它是預先分配好長度的,因此會出現溢位現象,而且刪除元素需要向隊頭移動一個位置,時間複雜度就變成 O(n)。因此,需要一種新的方式來解決這個問題,那就是迴圈佇列。
佇列的這種頭尾相接的順序儲存結構稱為迴圈佇列。為了避免佇列刪除元素需要移動整個佇列,使得隊頭和隊尾可以在陣列中迴圈變化。解決了移動元素的時間損耗,使得本來刪除是 O(n) 的時間複雜度變成了 O(1)。
1. 陣列式佇列
申請一片連續的儲存空間,並設定兩個指標進行管理。一個是隊頭指標front,它指向隊頭元素;另一個是隊尾指標rear,它指向下一個入隊元素的儲存位置。一般是實現迴圈佇列,隊頭和隊尾會隨著入隊和出隊的變化而變化的。例如 JDK 中 ArrayBlockingQueue 就是基於迴圈佇列來實現的。不過它會有溢位現象,一般解決方案是如果佇列滿了,設定入隊等待時間或者返回入隊不成功。一般在確定元素個數情況下使用,如果不確定元素個數,建議使用連結串列式佇列。

陣列式佇列
2. 連結串列式佇列
它就是基於連結串列儲存結構的佇列,可以動態的建立和刪除元素,不用關心佇列的長度,因此不用擔心溢位現象。新元素插入到隊尾,讀取的時候從隊頭開始,每次讀取一個元素,釋放一個元素,這就是所謂的動態建立和動態刪除。

連結串列式佇列
佇列有什麼用?
1. 保證輸入順序
比如吃飯排隊,先找服務員拿個號碼,上面會寫著前面還有 n 桌,這就相當於服務員把你加入了她們店的佇列中,當有空位置時,就直接叫你入座吃飯,當沒有空位子時,要麼排隊等候,要麼換一家吃飯。這就是佇列的用處。
2. 解耦
在系統設計中,好的設計是低耦合,高內聚。意思就是一個系統只做一件事情,把一件事情做好。既方便程式碼維護,又方便擴充套件。比如又一個下單的場景,使用者下單之後需要加積分,需要給各種優惠券等等。我們就可以利用佇列來解偶,但真實使用一般是用開源的訊息佇列,如Rocket MQ,Kafka 等等。
3. 提升系統吞吐量
就拿上面訂單來說,本來以前是使用者下單,給使用者新增積分,給使用者發優惠券是一起處理成功後返回的。現在只是處理下單,然後告訴某某系統,給某某使用者加積分,給某某使用者發優惠券了,它自己並不真正做這個動作,所以會提升系統響應,當需要保證最終一致性。一般也是用開源的訊息佇列來完成的。
佇列怎麼實現?
這裡是基於陣列的迴圈佇列實現,也就是 JDK 的 ArrayBlockingQueue。有興趣的朋友也可以看看基於連結串列的 LinkedBlockingQueue 的實現。
儲存結構
public class ArrayBlockingQueue<E> { //用陣列來儲存元素 Object[] items; //數組裡出隊的下標 int takeIndex; //入隊的下標 int putIndex; //元素個數 int count; }
入隊
public boolean offer(E e) { //加入陣列滿了,則返回入隊失敗 if (count == items.length) { return false; } else { //獲得當前陣列 Object[] items = this.items; //把元素 e 加入到隊尾 items[putIndex] = e; //判斷是不是隊尾是不是倒數第二個元素 //是的話把下標為 0 置為隊尾,說明一圈了 if (++putIndex == items.length) { putIndex = 0; } //總數加一 count++; return true; } }
出隊
public E poll() { //空佇列則返回 null if (count == 0) { return null; } else { //獲取當前陣列 Object[] items = this.items; //獲取隊頭元素 E item = (E) items[takeIndex]; //置空 items[takeIndex] = null; //重置隊頭為下標 0; if (++takeIndex == items.length) { takeIndex = 0; } //總數減一 count--; return item; } }
總結
佇列的作用還是很大的,比如保證輸入順序,解偶,提升系統吞吐量等等,都基於佇列的原理來實現的。佇列有陣列式佇列和連結串列式佇列。陣列式佇列就是基於陣列儲存結構來實現的,陣列的優缺點它全有。連結串列式佇列就是基於連結串列儲存結構來實現的,它也包含來連結串列的優缺點。所以在使用時可以根絕業務需求來選擇最優的方案。