關注公衆號 MageByte,設置星標獲取最新干貨。 「加羣」 進入技術交流羣獲更多技術成長。java
向固定大小的線程池投放請求任務時,若果線程池中沒有空閒資源了,這時候還有新的請求進來,線程池如何處理這個請求?拒絕請求仍是排隊?使用怎樣的處理機制git
通常兩種策略:github
那如何存儲排隊的請求呢?這就是今天要講的話題。算法
其底層的數據結構就是今天咱們要講的內容,「隊列」Queue
。數組
完整代碼詳見 GitHub:https://github.com/UniqueDong/algorithms.git瀏覽器
用一個生活例子,能夠想象成超市排隊結帳,先來的先結帳,後面的人只能站在末尾,不容許插隊。先進先出,這就是所謂的「隊列」緩存
隊列是一種線性數據結構,隊列的出口端叫「隊頭」,隊列的入口端叫「隊尾」。數據結構
與棧相似隊列的數據結構能夠使用數組實現也能夠使用鏈表實現。關於棧的內容同窗們能夠翻閱歷史文章學習「棧:實現瀏覽器前進後退」,隊列最基本的操做也是兩個:入隊 (enqueue) ,將新元素放到隊尾;出隊 (dequeue),從隊頭移除元素,出隊元素的下一個元素變成新的隊頭。併發
做爲基礎的數據結構,隊列的應用也很普遍,尤爲是一些特定場景下的隊列。好比循環隊列、阻塞隊列、併發隊列。它們在不少偏底層系統、框架、中間件的開發中,起着關鍵性的做用。好比高性能隊列 Disruptor、Linux 環形緩存,都用到了循環併發隊列;Java concurrent 併發包利用 ArrayBlockingQueue 來實現公平鎖等。框架
隊列也是一種操做受限的線性表數據結構。
隊列是跟棧同樣,是一種抽象的數據結構。 具備先進先出的特性,在隊頭刪除數據,在隊尾插入數據。
能夠使用數組實現,也能夠使用鏈表實現。使用數組實現的叫 順序隊列,用鏈表實現的 叫 鏈式隊列。
一塊兒先來看數組實現的隊列:
隨着不停地進行入隊、出隊操做,head 和 tail 都會持續日後移動。當 tail 移動到最右邊,即便數組中還有空閒空間,也沒法繼續往隊列中添加數據了。這個問題該如何解決呢?
當出現這種狀況的時候咱們就須要作數據遷移。如圖所示:當 abcd 入隊後,對應的指針位置。
如今咱們執行出隊操做
當咱們調用兩次出隊操做以後,隊列中 head 指針指向下標爲 2 的位置,tail 指針仍然指向下標爲 4 的位置。
遷移操做其實就是把整段數據移動到數組 0 開始的位置。
具體代碼以下
/** * 數組實現隊列 */ public class ArrayQueue<E> extends AbstractQueue<E> { /** * The queued items */ final E[] items; /** * 隊頭指針 */ private int front; /** * 隊尾指針 */ private int rear; /** * Creates an ArrayQueue with the given capacity * * @param capacity the capacity of this queue */ public ArrayQueue(Class<E> type, int capacity) { if (capacity <= 0) { throw new IllegalArgumentException(); } this.items = (E[]) Array.newInstance(type, capacity); } public int capacity() { return items.length; } @Override public E dequeue() { if (front == rear) { throw new IllegalStateException("Queue empty"); } return items[front++]; } @Override public boolean enqueue(E e) { if (isFull()) { throw new IllegalStateException("Queue empty"); } // 隊尾沒有空間了,須要執行數據遷移 if (rear == capacity()) { // 數據遷移 if (rear - front >= 0) { System.arraycopy(items, front, items, 0, rear - front); } // 調整 front 與 rear rear -= front; front = 0; } items[rear++] = e; return true; } @Override public boolean isFull() { return rear == capacity() && front == 0; } @Override public boolean isEmpty() { return front == rear; } }
咱們能夠經過以前學習過的鏈表來實現隊列,具體詳見單向鏈表篇 。其實主要就是利用了 出隊就是鏈表頭刪除數據,入隊就是尾節點添加數據
public class LinkedQueue<E> extends AbstractQueue<E> implements Queue<E> { private final SingleLinkedList<E> linkedList; public LinkedQueue() { this.linkedList = new SingleLinkedList<>(); } @Override public E dequeue() { if (linkedList.isEmpty()) { throw new IllegalStateException("Queue empty"); } return linkedList.remove(); } @Override public boolean enqueue(E e) { return linkedList.add(e); } @Override public boolean isFull() { return false; } @Override public boolean isEmpty() { return linkedList.isEmpty(); } }
剛剛的例子,當 rear == capacity
的時候,會出現數據遷移操做,這樣性能受到影響,那如何避免呢?
本來數組是有頭有尾的,是一條直線。如今咱們把首尾相連,扳成了一個環。
咱們能夠看到,圖中這個隊列的大小爲 8,當前 head=4,tail=7。當有一個新的元素 a 入隊時,咱們放入下標爲 7 的位置。但這個時候,咱們並不把 tail 更新爲 8,而是將其在環中後移一位,到下標爲 0 的位置。當再有一個元素 b 入隊時,咱們將 b 放入下標爲 0 的位置,而後 tail 加 1 更新爲 1。因此,在 a,b 依次入隊以後,循環隊列中的元素就變成了下面的樣子:
隊列爲空的判斷依然是 front == rear,隊列滿的條件則是 (rear + 1) % capacity = front
你有沒有發現,當隊列滿時,圖中的 tail 指向的位置其實是沒有存儲數據的。因此,循環隊列會浪費一個數組的存儲空間。
/** * 數組實現環形隊列 * * @param <E> */ public class ArrayCircleQueue<E> extends AbstractQueue<E> { /** * The queued items */ final E[] items; /** * 隊頭指針 */ private int front; /** * 隊尾指針 */ private int rear; public int capacity() { return items.length; } /** * Creates an ArrayQueue with the given capacity * * @param capacity the capacity of this queue */ public ArrayCircleQueue(Class<E> type, int capacity) { if (capacity <= 0) { throw new IllegalArgumentException(); } this.items = (E[]) Array.newInstance(type, capacity); } @Override public E dequeue() { if (front == rear) { throw new IllegalStateException("Queue empty"); } E item = items[front]; front = (front + 1) % items.length; return item; } @Override public boolean enqueue(E e) { checkNotNull(e); int newRear = (rear + 1) % items.length; if (newRear == front) { throw new IllegalStateException("Queue full"); } items[rear] = e; this.rear = newRear; return true; } @Override public boolean isFull() { return (rear + 1) % items.length == front; } @Override public boolean isEmpty() { return rear == front; } }
4.線性表之數組
5.鏈表導論-心法篇
7.雙向鏈表正確實現
原創不易,以爲有用但願隨手「在看」「收藏」「轉發」三連。