文章目錄
概述
一般我們認爲隊列都是先進先出的,在Java這裏是非常靈活的。文檔中有一句話是非常經典的:
Queues typically, but do not necessarily, order elements in a FIFO (first-in-first-out) manner. Among the exceptions are priority queues, which order elements according to a supplied comparator, or the elements’ natural ordering, and LIFO queues (or stacks) which order the elements LIFO (last-in-first-out). Whatever the ordering used, the head of the queue is that element which would be removed by a call to {@link #remove()} or {@link #poll()}. In a FIFO queue, all new elements are inserted at the tail of the queue. Other kinds of queues may use different placement rules. Every {@code Queue} implementation must specify its ordering properties.
意思是:
隊列通常(但不一定)以FIFO(先進先出)方式對元素排序。例外情況包括優先隊列(根據提供的比較器對元素排序)和LIFO隊列(或堆棧)(後進先出)。無論使用什麼順序,隊列的頭部都是通過調用{@link #remove()}或{@link #poll()}來刪除的元素。在FIFO隊列中,所有新元素都插入到隊列的尾部。其他類型的隊列可能使用不同的放置規則。每個{@code Queue}實現都必須指定其有序屬性。
Queue只是一個接口,具體的實現要靠子類來完成,如果想要使用優先隊列可以選擇 PriorityQueue ,如果僅僅就想用到隊列的普通功能一般會選擇LinkedList
我們看一下Queue接口的方法
(這裏忽略插入類型不滿足,插入的元素是空指針等的情況)
public interface Queue<E> extends Collection<E> {
boolean add(E e);
//如果可以在不違反容量限制的情況下立即將指定的元素插入此隊列,成功後返回{@code true},如果當前沒有空間可用,則拋出{@code IllegalStateException}。
boolean offer(E e);
//如果可以在不違反容量限制的情況下立即將指定的元素插入到此隊列中。當使用受容量限制的隊列時,此方法通常比{add}更可取,因爲{add}會因爲拋出異常而導致插入失敗。
//對比
//add()和offer()都是向隊列中添加一個元素。一些隊列有大小限制,因此如果想在一個滿的隊列中加入一個新項,調用 add()方法就會拋出一個 unchecked 異常,而調用 offer()方法會返回 false。因此就可以在程序中進行有效的判斷!
E remove();
E poll();
//類似於add()和offer()的關係
//remove()和 poll() 方法都是從隊列中刪除第一個元素。如果隊列元素爲空,調用remove() 的行爲與 Collection 接口的版本相似會拋出異常,但是新的 poll() 方法在用空集合調用時只是返回 null。因此新的方法更適合容易出現異常條件的情況。
E element();
E peek();
//類似於add()和offer()的關係
//element()和 peek() 用於在隊列的頭部檢索元素(不刪除),返回值是頭部元素。在隊列爲空時, element()拋出一個異常,而 peek()返回 null。
}
上面很詳細地說明了Queue接口定義的方法的要求的功能
下面看看PriorityQueue 它繼承自Queue,爲優先隊列。優先隊列是基於堆的。
鏈表,二叉查找樹,都可以提供插入和刪除最大值或者最小值這兩種操作,但是爲什麼不用它們卻偏偏使用堆。原因在於應用前兩者需要較高的時間複雜度。
對於鏈表的實現,插入需要O(1),刪除最小或最大需要遍歷鏈表,故需要O(N)。
當然這個看實現方式 ,插入與刪除最大或最小 難以兼得
對於二叉查找樹,這兩種操作都需要O(logN);而且隨着不停的刪除最小或最大的操作,二叉查找樹會變得非常不平衡;同時使用二叉查找樹有些浪費,因此很多操作根本不需要。
對於堆這兩種操作的最壞時間複雜度爲O(N),而插入的平均時間複雜度爲常數時間,即O(1)。
下面讓我們看一下源碼:
字段部分:
@SuppressWarnings("unchecked")
public class PriorityQueue<E> extends AbstractQueue<E>
implements java.io.Serializable {
private static final long serialVersionUID = -7720805057305804111L; //序列化版本ID
private static final int DEFAULT_INITIAL_CAPACITY = 11;
transient Object[] queue; // 用來存放元素
int size; //優先隊列的大小
private final Comparator<? super E> comparator; //比較器,用於元素排序
transient int modCount; // 與迭代器相關
對transient的解釋:
Java的serialization提供了一種持久化對象實例的機制。當持久化對象時,可能有一個特殊的對象數據成員,我們不想用serialization機制來保存它。爲了在一個特定對象的一個域上關閉serialization,可以在這個域前加上關鍵字transient。當一個對象被序列化的時候,transient型變量的值不包括在序列化的表示中,然而非transient型的變量是被包括進去的。
它的構造方法 主要涉及到優先隊列的創建(含大小的分配,默認爲11)
這裏展示了構造方法初始化優先隊列的情況 以及相關的方法
關於c instanceof SortedSet<?> 是判斷c是否是 SortedSet<?>的實例
以及c.getClass() == PriorityQueue.class 意思是c對應的Class類是否是PriorityQueue
一個類無論如何有多少個實例,這些實例都只對應一個Class類 也可以通過這個方法來判斷一個實例是不是另一個類的實例
//構造方法 創建一個初始容量爲11的優先隊列
public PriorityQueue() {
this(DEFAULT_INITIAL_CAPACITY, null); //第二個參數是比較器
}
//構造方法 創建一個初始容量爲指定值的優先隊列
public PriorityQueue(int initialCapacity) { //第二個參數是比較器
this(initialCapacity, null);
}
// 使用默認初始容量和創建PriorityQueue 其元素按照指定的比較器排序。
public PriorityQueue(Comparator<? super E> comparator) {
this(DEFAULT_INITIAL_CAPACITY, comparator);
}
//使用指定的初始容量創建PriorityQueue,該初始容量根據指定的比較器對其元素進行排序。
public PriorityQueue(int initialCapacity,
Comparator<? super E> comparator) {
// 實際上並不需要這樣的限制,但是對於JDK1.5兼容性來說,這種限制仍然存在,應該是當時Java編譯器上的兼容性問題
if (initialCapacity < 1)
throw new IllegalArgumentException();
this.queue = new Object[initialCapacity];
this.comparator = comparator;
}
//創建一個{PriorityQueue}包含指定集合中的元素。如果指定的集合是一個{SortedSet}的實例,或者是另一個{PriorityQueue},這個優先隊列將按照相同的順序排序。否則,該優先隊列將根據其元素的{ Comparable natural ordering}進行排序。
public PriorityQueue(Collection<? extends E> c) {
if (c instanceof SortedSet<?>) {
SortedSet<? extends E> ss = (SortedSet<? extends E>) c;
this.comparator = (Comparator<? super E>) ss.comparator(); //設置比較器
initElementsFromCollection(ss); //具體的實現方法在後面
}
else if (c instanceof PriorityQueue<?>) {
PriorityQueue<? extends E> pq = (PriorityQueue<? extends E>) c;
this.comparator = (Comparator<? super E>) pq.comparator(); //設置比較器
initFromPriorityQueue(pq); //具體的實現方法在後面
}
else {
this.comparator = null;
initFromCollection(c); //具體的實現方法在後面
}
}
//創建一個{PriorityQueue},其中包含指定優先隊列中的元素。這個優先隊列將按照與給定優先隊列相同的順序排序。
public PriorityQueue(PriorityQueue<? extends E> c) {
this.comparator = (Comparator<? super E>) c.comparator(); //設置比較器
initFromPriorityQueue(c); //具體的實現方法在後面
}
//創建一個{PriorityQueue},其中包含指定排序集中的元素。這個優先隊列將按照與給定排序集中相同的順序排序。
public PriorityQueue(SortedSet<? extends E> c) {
this.comparator = (Comparator<? super E>) c.comparator(); //設置比較器
initElementsFromCollection(c); //具體的實現方法在後面
}
/** 確保 queue[0] 存在 **/
private static Object[] ensureNonEmpty(Object[] es) {
return (es.length > 0) ? es : new Object[1];
}
private void initFromPriorityQueue(PriorityQueue<? extends E> c) {
if (c.getClass() == PriorityQueue.class) {
this.queue = ensureNonEmpty(c.toArray()); //複製元素
this.size = c.size(); //複製大小
} else {
initFromCollection(c);
}
}
private void initElementsFromCollection(Collection<? extends E> c) {
Object[] es = c.toArray(); //複製元素 爲了後面的處理
int len = es.length; //複製大小 爲了後面的處理
// If c.toArray incorrectly doesn't return Object[], copy it.
//如上句 就是排錯的
if (es.getClass() != Object[].class)
es = Arrays.copyOf(es, len, Object[].class);
//長度爲1||構造器不爲空 的情況下遍歷元素 確保元素沒有空值
if (len == 1 || this.comparator != null)
for (Object e : es)
if (e == null)
throw new NullPointerException();
this.queue = ensureNonEmpty(es); //複製元素
this.size = len; //複製大小
}
//使用給定集合中的元素初始化隊列數組。
private void initFromCollection(Collection<? extends E> c) {
initElementsFromCollection(c); //初始化
heapify(); //重新建堆
}
//返回包含此隊列中所有元素的數組。元素沒有特定的順序。
public Object[] toArray() {
return Arrays.copyOf(queue, size);
}
//返回包含此隊列中所有元素的數組;
//返回數組的類型是運行時的類型。 這個跟數組協變有關係
//
public <T> T[] toArray(T[] a) {
final int size = this.size;
if (a.length < size)
// Make a new array of a's runtime type, but my contents:
return (T[]) Arrays.copyOf(queue, size, a.getClass());
System.arraycopy(queue, 0, a, 0, size);
if (a.length > size)
a[size] = null;
return a;
}
上面的這段源碼結尾部分有heapify()這個方法,這個會在後面去說
下面關注優先隊列的方法了 開頭部分出現一個字段
private static final int MAX_ARRAY_SIZE = Integer.MAX_VALUE - 8;
這是限制要分配的數組的最大大小。一些虛擬機在數組中保留一些頭信息。試圖分配更大的數組可能會導致OutOfMemoryError:
請求的數組大小超過V虛擬機的限制。 除了主流的Oracle 的虛擬機,Java的虛擬機還有別的種類。 這個字段自然與下面的擴容相關的方法密切相關
//增加數組的容量。傳入的值是minCapacity 這個參數對後面的理解很關鍵
private void grow(int minCapacity) {
int oldCapacity = queue.length; //舊容量大小
// Double size if small; else grow by 50%
//如果舊容量大小小於64 就加2
//如果舊容量大小>=64 就乘以2 >>是右移一位呀
int newCapacity = oldCapacity + ((oldCapacity < 64) ?
(oldCapacity + 2) :
(oldCapacity >> 1));
// overflow-conscious code
if (newCapacity - MAX_ARRAY_SIZE > 0) //要求的新容量值大於最大限制了
newCapacity = hugeCapacity(minCapacity);
queue = Arrays.copyOf(queue, newCapacity);
}
//判斷傳入的值 是小於0 還是大於最大限制值的問題
private static int hugeCapacity(int minCapacity) {
if (minCapacity < 0) // overflow
throw new OutOfMemoryError();
return (minCapacity > MAX_ARRAY_SIZE) ?
Integer.MAX_VALUE :
MAX_ARRAY_SIZE;
}
優先隊列核心方法
//*********************************************
//將指定的元素插入此優先隊列。
public boolean add(E e) {
return offer(e); //這裏用到了offer方法
}
//將指定的元素插入此優先隊列。
public boolean offer(E e) {
if (e == null)
throw new NullPointerException();
modCount++;
int i = size;
if (i >= queue.length)
grow(i + 1);
siftUp(i, e);
size = i + 1;
return true;
}
//對於優先隊列而言 offer和add方法是相同的
//*********************************************
//獲取優先隊列開頭的元素
public E peek() {
return (E) queue[0];
}
//這個是配合下面remove方法的 遍歷尋找數組中第一個滿足條件的元素 有的話返回索引 沒有的話返回-1
private int indexOf(Object o) {
if (o != null) {
final Object[] es = queue;
for (int i = 0, n = size; i < n; i++)
if (o.equals(es[i]))
return i;
}
return -1;
}
public boolean remove(Object o) {
int i = indexOf(o); //獲取索引
if (i == -1)
return false;
else {
removeAt(i); //移除索引爲i的元素 具體實現看下面
return true;
}
}
//如果此隊列包含指定的元素,則返回{true}。更正式地說,如果且僅當此隊列包含至少一個元素{ e},使得{ o.equals(e)},則返回{true}。
public boolean contains(Object o) {
return indexOf(o) >= 0;
}
//返回大小
public int size() {
return size;
}
//從該優先級隊列中刪除所有元素。此調用返回後,隊列將爲空。
public void clear() {
modCount++;
final Object[] es = queue;
for (int i = 0, n = size; i < n; i++)
es[i] = null;
size = 0; //大小設置爲0
}
//刪除第一個元素並且重建堆
public E poll() {
final Object[] es;
final E result;
if ((result = (E) ((es = queue)[0])) != null) {
modCount++;
final int n;
final E x = (E) es[(n = --size)];
es[n] = null;
if (n > 0) {
final Comparator<? super E> cmp;
if ((cmp = comparator) == null)
siftDownComparable(0, x, es, n);
else
siftDownUsingComparator(0, x, es, n, cmp);
}
}
return result;
}
//移除特定位置的元素 並且重建堆
E removeAt(int i) {
// assert i >= 0 && i < size;
final Object[] es = queue;
modCount++;
int s = --size;
if (s == i) // removed last element
es[i] = null;
else {
E moved = (E) es[s];
es[s] = null;
siftDown(i, moved);
if (es[i] == moved) {
siftUp(i, moved);
if (es[i] != moved)
return moved;
}
}
return null;
}
//堆的上升 選擇比較器 進行重建堆
private void siftUp(int k, E x) {
if (comparator != null)
siftUpUsingComparator(k, x, queue, comparator);
else
siftUpComparable(k, x, queue);
}
//堆上升的比較方法 從方法中可以看出堆是從索引爲0的地方開始的 而且這是一個
private static <T> void siftUpComparable(int k, T x, Object[] es) {
Comparable<? super T> key = (Comparable<? super T>) x;
while (k > 0) {
int parent = (k - 1) >>> 1; //a >>> b a表要操作數 b表要移的位數
Object e = es[parent];
if (key.compareTo((T) e) >= 0)
break;
es[k] = e;
k = parent;
}
es[k] = key;
}
//使用特定的比較器進行比較
private static <T> void siftUpUsingComparator(
int k, T x, Object[] es, Comparator<? super T> cmp) {
while (k > 0) {
int parent = (k - 1) >>> 1;
Object e = es[parent];
if (cmp.compare(x, (T) e) >= 0)
break;
es[k] = e;
k = parent;
}
es[k] = x;
}
//堆的下沉 選擇比較器 進行重建堆
private void siftDown(int k, E x) {
if (comparator != null)
siftDownUsingComparator(k, x, queue, size, comparator);
else
siftDownComparable(k, x, queue, size);
}
private static <T> void siftDownComparable(int k, T x, Object[] es, int n) {
// assert n > 0;
Comparable<? super T> key = (Comparable<? super T>)x;
int half = n >>> 1; // loop while a non-leaf
while (k < half) {
int child = (k << 1) + 1; // assume left child is least
Object c = es[child];
int right = child + 1;
if (right < n &&
((Comparable<? super T>) c).compareTo((T) es[right]) > 0)
c = es[child = right];
if (key.compareTo((T) c) <= 0)
break;
es[k] = c;
k = child;
}
es[k] = key;
}
private static <T> void siftDownUsingComparator(
int k, T x, Object[] es, int n, Comparator<? super T> cmp) {
// assert n > 0;
int half = n >>> 1;
while (k < half) {
int child = (k << 1) + 1;
Object c = es[child];
int right = child + 1;
if (right < n && cmp.compare((T) c, (T) es[right]) > 0)
c = es[child = right];
if (cmp.compare(x, (T) c) <= 0)
break;
es[k] = c;
k = child;
}
es[k] = x;
}
//在整個樹中重建堆,不考慮調用之前元素的順序
private void heapify() {
final Object[] es = queue;
int n = size, i = (n >>> 1) - 1;
final Comparator<? super E> cmp;
if ((cmp = comparator) == null)
for (; i >= 0; i--)
siftDownComparable(i, (E) es[i], es, n);
else
for (; i >= 0; i--)
siftDownUsingComparator(i, (E) es[i], es, n, cmp);
}
//返回用於對該隊列中的元素排序的比較器,如果該隊列按照其元素的{Comparable natural order}排序,則返回{ null}
public Comparator<? super E> comparator() {
return comparator;
}
剩下的內容就是和迭代器相關的了,以後會詳細對迭代器有個全面的介紹 貼出源碼供參考
//查閱優先隊列的繼承體系,它繼承自collection,collection最上面繼承了Itr 這個是與 Itr.remove.有管的方法
void removeEq(Object o) {
final Object[] es = queue;
for (int i = 0, n = size; i < n; i++) {
if (o == es[i]) {
removeAt(i);
break;
}
}
}
//迭代器部分 暫且不說以後會詳細說一下的
public Iterator<E> iterator() {
return new Itr();
}
private final class Itr implements Iterator<E> {
private int cursor;
private int lastRet = -1;
private ArrayDeque<E> forgetMeNot;
private E lastRetElt;
private int expectedModCount = modCount;
Itr() {} // prevent access constructor creation
public boolean hasNext() {
return cursor < size ||
(forgetMeNot != null && !forgetMeNot.isEmpty());
}
public E next() {
if (expectedModCount != modCount)
throw new ConcurrentModificationException();
if (cursor < size)
return (E) queue[lastRet = cursor++];
if (forgetMeNot != null) {
lastRet = -1;
lastRetElt = forgetMeNot.poll();
if (lastRetElt != null)
return lastRetElt;
}
throw new NoSuchElementException();
}
public void remove() {
if (expectedModCount != modCount)
throw new ConcurrentModificationException();
if (lastRet != -1) {
E moved = PriorityQueue.this.removeAt(lastRet);
lastRet = -1;
if (moved == null)
cursor--;
else {
if (forgetMeNot == null)
forgetMeNot = new ArrayDeque<>();
forgetMeNot.add(moved);
}
} else if (lastRetElt != null) {
PriorityQueue.this.removeEq(lastRetElt);
lastRetElt = null;
} else {
throw new IllegalStateException();
}
expectedModCount = modCount;
}
}
//將此隊列保存到流中(即序列化它)。
private void writeObject(java.io.ObjectOutputStream s)
throws java.io.IOException {
// Write out element count, and any hidden stuff
s.defaultWriteObject();
// Write out array length, for compatibility with 1.5 version
s.writeInt(Math.max(2, size + 1));
// Write out all elements in the "proper order".
final Object[] es = queue;
for (int i = 0, n = size; i < n; i++)
s.writeObject(es[i]);
}
//從流(即反序列化)中重新構造{PriorityQueue}實例。
private void readObject(java.io.ObjectInputStream s)
throws java.io.IOException, ClassNotFoundException {
// Read in size, and any hidden stuff
s.defaultReadObject();
// Read in (and discard) array length
s.readInt();
SharedSecrets.getJavaObjectInputStreamAccess().checkArray(s, Object[].class, size);
final Object[] es = queue = new Object[Math.max(size, 1)];
// Read in all elements.
for (int i = 0, n = size; i < n; i++)
es[i] = s.readObject();
// Elements are guaranteed to be in "proper order", but the
// spec has never explained what that might be.
heapify();
}
public final Spliterator<E> spliterator() {
return new PriorityQueueSpliterator(0, -1, 0);
}
final class PriorityQueueSpliterator implements Spliterator<E> {
private int index; // current index, modified on advance/split
private int fence; // -1 until first use
private int expectedModCount; // initialized when fence set
/** Creates new spliterator covering the given range. */
PriorityQueueSpliterator(int origin, int fence, int expectedModCount) {
this.index = origin;
this.fence = fence;
this.expectedModCount = expectedModCount;
}
private int getFence() { // initialize fence to size on first use
int hi;
if ((hi = fence) < 0) {
expectedModCount = modCount;
hi = fence = size;
}
return hi;
}
public PriorityQueueSpliterator trySplit() {
int hi = getFence(), lo = index, mid = (lo + hi) >>> 1;
return (lo >= mid) ? null :
new PriorityQueueSpliterator(lo, index = mid, expectedModCount);
}
public void forEachRemaining(Consumer<? super E> action) {
if (action == null)
throw new NullPointerException();
if (fence < 0) { fence = size; expectedModCount = modCount; }
final Object[] es = queue;
int i, hi; E e;
for (i = index, index = hi = fence; i < hi; i++) {
if ((e = (E) es[i]) == null)
break; // must be CME
action.accept(e);
}
if (modCount != expectedModCount)
throw new ConcurrentModificationException();
}
public boolean tryAdvance(Consumer<? super E> action) {
if (action == null)
throw new NullPointerException();
if (fence < 0) { fence = size; expectedModCount = modCount; }
int i;
if ((i = index) < fence) {
index = i + 1;
E e;
if ((e = (E) queue[i]) == null
|| modCount != expectedModCount)
throw new ConcurrentModificationException();
action.accept(e);
return true;
}
return false;
}
public long estimateSize() {
return getFence() - index;
}
public int characteristics() {
return Spliterator.SIZED | Spliterator.SUBSIZED | Spliterator.NONNULL;
}
}
/**
* @throws NullPointerException {@inheritDoc}
*/
public boolean removeIf(Predicate<? super E> filter) {
Objects.requireNonNull(filter);
return bulkRemove(filter);
}
/**
* @throws NullPointerException {@inheritDoc}
*/
public boolean removeAll(Collection<?> c) {
Objects.requireNonNull(c);
return bulkRemove(e -> c.contains(e));
}
/**
* @throws NullPointerException {@inheritDoc}
*/
public boolean retainAll(Collection<?> c) {
Objects.requireNonNull(c);
return bulkRemove(e -> !c.contains(e));
}
// A tiny bit set implementation
private static long[] nBits(int n) {
return new long[((n - 1) >> 6) + 1];
}
private static void setBit(long[] bits, int i) {
bits[i >> 6] |= 1L << i;
}
private static boolean isClear(long[] bits, int i) {
return (bits[i >> 6] & (1L << i)) == 0;
}
/** Implementation of bulk remove methods. */
private boolean bulkRemove(Predicate<? super E> filter) {
final int expectedModCount = ++modCount;
final Object[] es = queue;
final int end = size;
int i;
// Optimize for initial run of survivors
for (i = 0; i < end && !filter.test((E) es[i]); i++)
;
if (i >= end) {
if (modCount != expectedModCount)
throw new ConcurrentModificationException();
return false;
}
// Tolerate predicates that reentrantly access the collection for
// read (but writers still get CME), so traverse once to find
// elements to delete, a second pass to physically expunge.
final int beg = i;
final long[] deathRow = nBits(end - beg);
deathRow[0] = 1L; // set bit 0
for (i = beg + 1; i < end; i++)
if (filter.test((E) es[i]))
setBit(deathRow, i - beg);
if (modCount != expectedModCount)
throw new ConcurrentModificationException();
int w = beg;
for (i = beg; i < end; i++)
if (isClear(deathRow, i - beg))
es[w++] = es[i];
for (i = size = w; i < end; i++)
es[i] = null;
heapify();
return true;
}
/**
* @throws NullPointerException {@inheritDoc}
*/
public void forEach(Consumer<? super E> action) {
Objects.requireNonNull(action);
final int expectedModCount = modCount;
final Object[] es = queue;
for (int i = 0, n = size; i < n; i++)
action.accept((E) es[i]);
if (expectedModCount != modCount)
throw new ConcurrentModificationException();
}
}