1. 程式人生 > >Java 集合深入理解(9):Queue 佇列

Java 集合深入理解(9):Queue 佇列

今天心情不太好,來學一下 List 吧!

什麼是佇列

佇列是資料結構中比較重要的一種型別,它支援 FIFO,尾部新增、頭部刪除(先進佇列的元素先出佇列),跟我們生活中的排隊類似。

佇列有兩種:

  • 單佇列
  • 迴圈佇列

單佇列就是常見的佇列, 每次新增元素時,都是新增到隊尾:

以陣列實現的佇列為例,初始時佇列長度固定為 4,font 和 rear 均為 0:

這裡寫圖片描述

每新增一個元素,rear 後移一位。當新增四個元素後, rear 到了索引為 4 的位置:

這裡寫圖片描述

這時 a1,a2 出隊,front 後移動到 2:

這裡寫圖片描述

這時想要再新增兩個元素,但 rear 後移兩位後就會越界:

這裡寫圖片描述

明明有三個空位,卻只能再放入一個!這就是單佇列的“假溢位”情況。

針對這種情況,解決辦法就是後面滿了,就再從頭開始,也就是頭尾相接的迴圈。這就是 “迴圈佇列” 的概念。

迴圈佇列:

迴圈佇列中,
rear = (rear - size) % size

接著上面的例子,當 rear 大於 佇列長度時,rear = ( 5 - 5) % 5 = 0 :

這裡寫圖片描述

這樣繼續新增時,還可以新增幾個元素:

這裡寫圖片描述

那如何判斷佇列是否裝滿元素了呢,單使用 front == rear 無法判斷究竟是空的還是滿了。

兩種方法:

  1. 加個標誌 flag ,初始為 false,新增滿了置為 true;
  2. 不以 front = rear 為放滿標誌,改為 (rear - front) % size = 1。

法 2 的公式放滿元素時空餘了一個位置,這個公式是什麼意思呢?

這裡寫圖片描述

接著上面的情況,當 rear 從後面新增元素跑到前面 0 時,再新增一個元素 a6,rear 後移一位到 1,這時 front = 2, (1 - 2) % 5 = 1, 滿足放滿條件。

因此,當 rear > font 時,佇列中元素個數 = rear - font;

當 rear < font 時,佇列中元素分為兩部分: size - font 和 rear ,也就是 rear + size - font。以上述圖片為例,佇列中元素個數 = 1 + 5 - 2 = 4.

這裡寫圖片描述

接著我們介紹 Java 集合框架中的佇列 Queue

這裡寫圖片描述

Java 集合中的 Queue 繼承自 Collection 介面 ,Deque, LinkedList, PriorityQueue, BlockingQueue 等類都實現了它。

Queue 用來存放 等待處理元素 的集合,這種場景一般用於緩衝、併發訪問。

除了繼承 Collection 介面的一些方法,Queue 還添加了額外的 新增、刪除、查詢操作。

這裡寫圖片描述

新增、刪除、查詢這些個操作都提供了兩種形式,其中一種在操作失敗時直接丟擲異常,而另一種則返回一個特殊的值:

這裡寫圖片描述

Queue 方法介紹:

1.add(E), offer(E) 在尾部新增:

boolean add(E e);

boolean offer(E e);

他們的共同之處是建議實現類禁止新增 null 元素,否則會報空指標 NullPointerException;

不同之處在於 add() 方法在新增失敗(比如佇列已滿)時會報 一些執行時錯誤 錯;而 offer() 方法即使在新增失敗時也不會奔潰,只會返回 false。

2016.11.21 新增

注意

Queue 是個介面,它提供的 add, offer 方法初衷是希望子類能夠禁止新增元素為 null,這樣可以避免在查詢時返回 null 究竟是正確還是錯誤。

事實上大多數 Queue 的實現類的確響應了 Queue 介面的規定,比如 ArrayBlockingQueue,PriorityBlockingQueue 等等。

但還是有一些實現類沒有這樣要求,比如 LinkedList。

2.remove(), poll() 刪除並返回頭部:

E remove();

E poll();

當佇列為空時 remove() 方法會報 NoSuchElementException 錯; 而 poll() 不會奔潰,只會返回 null。

3.element(), peek() 獲取但不刪除:

E element();

E peek();

當佇列為空時 element() 丟擲異常;peek() 不會奔潰,只會返回 null。

其他

1.雖然 LinkedList 沒有禁止新增 null,但是一般情況下 Queue 的實現類都不允許新增 null 元素,為啥呢?因為 poll(), peek() 方法在異常的時候會返回 null,你添加了 null 以後,當獲取時不好分辨究竟是否正確返回。

2.Queue 一般都是 FIFO 的,但是也有例外,比如優先佇列 priority queue(它的順序是根據自然排序或者自定義 comparator 的);再比如 LIFO 的佇列(跟棧一樣,後來進去的先出去)。

不論進入、出去的先後順序是怎樣的,使用 remove(),poll() 方法操作的都是 頭部 的元素;而插入的位置則不一定是在隊尾了,不同的 queue 會有不同的插入邏輯。

Thanks