在開始很重要的集合Map的學習以前,咱們先學習一下集合Queue,主要介紹一下集合Queue的幾個重要的實現類。雖然它的內容很少,但它牽涉到了極其重要的數據結構:隊列。因此此次主要針對隊列這種數據結構的使用來介紹Queue中的實現類。java
隊列與棧是相對的一種數據結構。只容許在一端進行插入操做,而在另外一端進行刪除操做的線性表。棧的特色是後進先出,而隊列的特色是先進先出。隊列的用處很大,但大多都是在其餘的數據結構中,好比,樹的按層遍歷,圖的廣度優先搜索等都須要使用隊列作爲輔助數據結構。編程
單向隊列比較簡單,只能向隊尾添加元素,從隊頭刪除元素。好比最典型的排隊買票例子,新來的人只能在隊列後面,排到最前邊的人才能夠買票,買完了之後,離開隊伍。這個過程是一個很是典型的隊列。
定義隊列的接口:數組
public interface Queue {
public boolean add(Object elem); // 將一個元素放到隊尾,若是成功,返回true
public Object remove(); // 將一個元素從隊頭刪除,若是成功,返回true
}
複製代碼
一個隊列只要能入隊,和出隊就能夠了。這個隊列的接口就定義好了,具體的實現有不少種辦法,例如,可使用數組作存儲,可使用鏈表作存儲。
其實你們頁能夠看一下JDK源碼,在java.util.Queue中,能夠看到隊列的定義。只是它是泛型的。基本上,Queue.java中定義的接口都是進隊,出隊。只是行爲有所不一樣。例如,remove若是失敗,會拋出異常,而poll失敗則返回null,但它倆其實都是從隊頭刪除元素。安全
若是一個隊列的頭和尾都支持元素入隊,出隊,那麼這種隊列就稱爲雙向隊列,英文是Deque。你們能夠經過java.util.Deque來查看Deque的接口定義,這裏節選一部分:bash
public interface Deque<E> extends Queue<E> {
/**
* Inserts the specified element at the front of this deque if it is
* possible to do so immediately without violating capacity restrictions,
* throwing an {@code IllegalStateException} if no space is currently
* available. When using a capacity-restricted deque, it is generally
* preferable to use method {@link #offerFirst}.
*
* @param e the element to add
* @throws IllegalStateException if the element cannot be added at this
* time due to capacity restrictions
* @throws ClassCastException if the class of the specified element
* prevents it from being added to this deque
* @throws NullPointerException if the specified element is null and this
* deque does not permit null elements
* @throws IllegalArgumentException if some property of the specified
* element prevents it from being added to this deque
*/
void addFirst(E e);
void addLast(E e);
E removeFirst();
E removeLast();
}
複製代碼
最重要的也就是這4個,一大段英文,沒啥意思,其實就是說,addFirst是向隊頭添加元素,若是不知足條件就會拋異常,而後定義了各類狀況下拋出的異常類型。
只要記住隊列是先進先出的數據結構就行了,今天沒必要要把這些東西都掌握,一步步來。數據結構
Queue也繼承自Collection,用來存放等待處理的集合,這種場景通常用於緩衝、併發訪問。咱們先看一下官方的定義和類結構:併發
/**
* A collection designed for holding elements prior to processing.
* Besides basic {@link java.util.Collection Collection} operations,
* queues provide additional insertion, extraction, and inspection
* operations. Each of these methods exists in two forms: one throws
* an exception if the operation fails, the other returns a special
* value (either {@code null} or {@code false}, depending on the
* operation). The latter form of the insert operation is designed
* specifically for use with capacity-restricted {@code Queue}
* implementations; in most implementations, insert operations cannot
* fail.
*/
複製代碼
意思大致說Queue是用於在處理以前保存元素的集合。 除了基本的集合操做,隊列提供了額外的插入、提取和檢查操做。 每一個方法都有兩種形式:一種是在操做失敗時拋出一個異常,另外一個則返回一個特殊值(根據操做的不一樣)(返回null或false)。 插入操做的後一種形式是專門爲有容量限制的隊列實現而設計的; 在大多數實現中,插入操做不會失敗。ide
public interface Queue<E> extends Collection<E> {
//插入(拋出異常)
boolean add(E e);
//插入(返回特殊值)
boolean offer(E e);
//移除(拋出異常)
E remove();
//移除(返回特殊值)
E poll();
//檢查(拋出異常)
E element();
//檢查(返回特殊值)
E peek();
}
複製代碼
能夠看出Queue接口沒有什麼神祕面紗,都不須要揭開。不存在花裏胡哨,就只有這6個方法。額外的添加、刪除、查詢操做。
值得一提的是,Queue是個接口,它提供的add,offer方法初衷是但願子類可以禁止添加元素爲null,這樣能夠避免在查詢時返回null到底是正確仍是錯誤。實際上大多數Queue的實現類的確響應了Queue接口的規定,好比ArrayBlockingQueue,PriorityBlockingQueue等等。
但仍是有一些實現類沒有這樣要求,好比LinkedList。
雖然 LinkedList 沒有禁止添加 null,可是通常狀況下 Queue 的實現類都不容許添加 null 元素,爲啥呢?由於poll(),peek()方法在異常的時候會返回 null,你添加了null 之後,當獲取時很差分辨到底是否正確返回。post
PriorityQueue又叫作優先級隊列,保存隊列元素的順序不是按照及加入隊列的順序,而是按照隊列元素的大小進行從新排序。所以當調用peek()或pool()方法取出隊列中頭部的元素時,並非取出最早進入隊列的元素,而是取出隊列的最小元素。
咱們剛剛纔說到隊列的特色是先進先出,爲何這裏就按照大小順序排序了呢?咱們仍是先看一下它的介紹,直接翻譯過來:性能
基於優先級堆的無界的優先級隊列。
PriorityQueue的元素根據天然排序進行排序,或者按隊列構建時提供的 Comparator進行排序,具體取決於使用的構造方法。
優先隊列不容許 null 元素。
經過天然排序的PriorityQueue不容許插入不可比較的對象。
該隊列的頭是根據指定排序的最小元素。
若是多個元素都是最小值,則頭部是其中的一個元素——任意選取一個。
隊列檢索操做poll、remove、peek和element訪問隊列頭部的元素。
優先隊列是無界的,但有一個內部容量,用於管理用於存儲隊列中元素的數組的大小。
基本上它的大小至少和隊列大小同樣大。
當元素被添加到優先隊列時,它的容量會自動增加。增加策略的細節沒有指定。
複製代碼
一句話歸納,PriorityQueue使用了一個高效的數據結構:堆。底層是使用數組保存數據。還會進行排序,優先將元素的最小值存到隊頭。
PriorityQueue中的元素能夠默認天然排序或者經過提供的Comparator(比較器)在隊列實例化時指定的排序方式進行排序。關於天然排序與Comparator(比較器)能夠參考個人上一篇文章Java集合(六) Set詳解的講解。因此這裏的用法就不復述了。
須要注意的是,當PriorityQueue中沒有指定的Comparator時,加入PriorityQueue的元素必須實現了Comparable接口(元素是能夠進行比較的),不然會致使 ClassCastException。
PriorityQueue 本質也是一個動態數組,在這一方面與ArrayList是一致的。看一下它的構造方法:
public PriorityQueue(int initialCapacity) {
this(initialCapacity, null);
}
public PriorityQueue(Comparator<? super E> comparator) {
this(DEFAULT_INITIAL_CAPACITY, comparator);
}
public PriorityQueue(int initialCapacity,
Comparator<? super E> comparator) {
// Note: This restriction of at least one is not actually needed,
// but continues for 1.5 compatibility
if (initialCapacity < 1)
throw new IllegalArgumentException();
this.queue = new Object[initialCapacity];
this.comparator = comparator;
}
複製代碼
DEFAULT_IITIAL_CAPACITY = 11
)建立一個PriorityQueue,並根據其天然順序來排序其元素(使用加入其中的集合元素實現的Comparable)。 Deque接口是Queue接口子接口。它表明一個雙端隊列。Deque接口在Queue接口的基礎上增長了一些針對雙端添加和刪除元素的方法。LinkedList也實現了Deque接口,因此也能夠被看成雙端隊列使用。也能夠看前面的 Java集合(四) LinkedList詳解來理解Deque接口。
先瞄一眼類結構:
public interface Deque<E> extends Queue<E> {
//從頭部插入(拋異常)
void addFirst(E e);
//從尾部插入(拋異常)
void addLast(E e);
//從頭部插入(特殊值)
boolean offerFirst(E e);
//從尾部插入(特殊值)
boolean offerLast(E e);
//從頭部移除(拋異常)
E removeFirst();
//從尾部移除(拋異常)
E removeLast();
//從頭部移除(特殊值)
E pollFirst();
//從尾部移除(特殊值)
E pollLast();
//從頭部查詢(拋異常)
E getFirst();
//從尾部查詢(拋異常)
E getLast();
//從頭部查詢(特殊值)
E peekFirst();
//從尾部查詢(特殊值)
E peekLast();
//(從頭至尾遍歷列表時,移除列表中第一次出現的指定元素)
boolean removeFirstOccurrence(Object o);
//(從頭至尾遍歷列表時,移除列表中最後一次出現的指定元素)
boolean removeLastOccurrence(Object o);
//都沒啥難度,不解釋了
boolean add(E e);
boolean offer(E e);
E remove();
E poll();
E element();
E peek();
void push(E e);
E pop();
boolean remove(Object o);
boolean contains(Object o);
public int size();
Iterator<E> iterator();
Iterator<E> descendingIterator();
}
複製代碼
從上面的方法能夠看出,Deque不只能夠當成雙端隊列使用,並且能夠被當成棧來使用,由於該類中還包含了pop(出棧)、push(入棧)兩個方法。其餘基本就是方法名後面加上「First」和「Last」代表在哪端操做。
重頭戲來了,顧名思義,ArrayDeque使用數組實現的Deque;底層是數組,也是能夠指定它的capacity,固然也能夠不指定,默認長度是16,根據添加的元素個數,動態擴容。
值得重點介紹的是,ArrayDeque是一個循環隊列。它的實現比較高效,它的思路是這樣:引入兩個遊標,head 和 tail,若是向隊列裏,插入一個元素,就把 tail 向後移動。若是從隊列中刪除一個元素,就把head向後移動。咱們看一下示意圖:
全部的集合類都會面臨一個問題,那就是若是容器中的空間不夠了怎麼辦。這就涉及到擴容的問題。在前面咱們已經說了,咱們要保證數組的長度都是2的整數次冪,那麼擴容的時候也很簡單,直接把原來的數組長度乘以2就能夠了。申請一個長度爲原數組兩倍的數組,而後把數據拷貝進去就OK了。咱們看一下具體代碼:
private void doubleCapacity() {
assert head == tail;
int p = head;
int n = elements.length;
int r = n - p; // number of elements to the right of p
int newCapacity = n << 1;
if (newCapacity < 0)
throw new IllegalStateException("Sorry, deque too big");
Object[] a = new Object[newCapacity];
System.arraycopy(elements, p, a, 0, r);
System.arraycopy(elements, 0, a, r, p);
elements = a;
head = 0;
tail = n;
}
複製代碼
代碼沒啥難度,先把長度擴一倍,(n<<1),再把數據拷到目標位置。只要把這兩個arraycopy方法看懂問題不大。