Queue(隊列)是擁有先進先出(FIFO)特性的數據結構,PriorityQueue(優先級隊列)是它的子類之一,不一樣於先進先出,它能夠經過比較器控制元素的輸出順序(優先級)。本文就來分析一下PriorityQueuede的源碼,看看它是如何實現的。java
先來看Queue
接口:數組
public interface Queue<E> extends Collection<E> 複製代碼
Queue接口繼承了Collection接口,表示集合。它提供了三種方法,即:增長、刪除、獲取,每種都有兩個實現。安全
// 增長元素
boolean add(E e);
boolean offer(E e);
// 刪除元素
E remove();
E poll();
// 獲取元素
E element();
E peek();
複製代碼
兩種實現的區別是,add
、remove
和element
都比對應的offer
、poll
和peek
多拋出一個異常。對於add
方法,若是由於容量不足以增長新元素,會拋出IllegalStateException
異常,而offer
則會返回false。對於remove
方法,若是隊列是空的,則會拋出NoSuchElementException
異常,而poll
方法會返回null。對於element
方法,若是隊列是空的,則會拋出NoSuchElementException
異常,而peek
方法會返回null。
微信
再看PriorityQueue
類:數據結構
public class PriorityQueue<E> extends AbstractQueue<E> implements java.io.Serializable 複製代碼
嗯,不是直接實現Queue
,而是繼承了AbstractQueue
類。又是熟悉的感受,AbstractQueue
應該也是模板抽象類(和AbstractMap和AbstractList相似)。
來看AbstractQueue
:多線程
public abstract class AbstractQueue<E> extends AbstractCollection<E> implements Queue<E> 複製代碼
果真,繼承了AbstractCollection
類和實現了Queue
接口。既然是模板類那確定有模板方法。AbstractQueue
源碼中只實現了add
、remove
和elemet
方法。函數
public boolean add(E e) {
if (offer(e)) // 調用offer
...
}
public E remove() {
E x = poll(); // 調用poll
...
}
public E element() {
E x = peek(); // 調用peek
...
}
複製代碼
能夠看到,它們分別調用了offer
、poll
和peek
。也就是說,若是要經過AbstractQueue
實現隊列,則必須實現offer
、poll
和peek
方法。
下面就正緊的分析PriorityQueue
的源碼了。源碼分析
縱觀源碼,發現PriorityQueue是經過「極大優先級堆」實現的,即堆頂元素是優先級最大的元素。算是集成了大根堆和小根堆的功能。this
根據堆的特性,存儲結構確定是數組了;既然支持不一樣優先級,確定有比較器,也就是說支持自定義排序和順序排序。spa
// 默認容量11
private static final int DEFAULT_INITIAL_CAPACITY = 11;
// 堆的存儲結構,存儲元素
transient Object[] queue; // 不可序列化
// 當前存儲的元素數量
int size;
// 比較器,肯定優先級高低
private final Comparator<? super E> comparator;
// 避免OOM,數組能夠分配的最大容量
private static final int MAX_ARRAY_SIZE = Integer.MAX_VALUE - 8;
複製代碼
PriorityQueue的構造函數有不少,主要參數是容量和比較器:
public PriorityQueue() {
this(DEFAULT_INITIAL_CAPACITY, null); // 默認天然序
}
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) {
... // 對應賦值
}
public PriorityQueue(Collection<? extends E> c) {
// 將c轉成堆
if (c instanceof SortedSet<?>) {
... // SortedSet自帶比較器
}
else if (c instanceof PriorityQueue<?>) {
... // PriorityQueue自帶比較器
}
else {
...
}
}
public PriorityQueue(PriorityQueue<? extends E> c) {
... // 從PriorityQueue獲取比較器,將c轉成堆
}
public PriorityQueue(SortedSet<? extends E> c) {
...// 從SortedSet獲取比較器,將c轉成堆
}
複製代碼
PriorityQueue是支持擴容的,先來看擴容方法:
// minCapacity表示須要的最小容量
private void grow(int minCapacity) {
int oldCapacity = queue.length; // 獲取當前容量
// Double size if small; else grow by 50%
// 若是舊容量小於64,則增長舊容量+2的大小
// 若是舊容量大於等於64,則增長舊容量的一半大小
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); // 複製已存儲的數據
}
複製代碼
每次擴展的容量大小仍是挺大的,要麼變爲原來的雙倍,要麼增加一半大小。
在看增長元素、刪除元素和獲取元素的方法以前,先了解如下幾點:
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; // 當前大小加1
return true;
}
複製代碼
siftUp
方法最終會調用siftUpUsingComparator
或者siftUpComparable
方法,兩個實現相似,這裏直接看siftUpUsingComparator
方法。
// 上移就是不斷和父結點比較
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;
}
複製代碼
每次增長元素,都要保證堆序。
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;
}
複製代碼
poll方法會返回隊首元素(堆頂),並將元素從堆中刪除。刪除過程,是將第一個元素與最後一個元素進行替換,而後再進行下移調整操做。這裏直接看siftDownUsingComparator
方法:
private static <T> void siftDownUsingComparator( int k, T x, Object[] es, int n, Comparator<? super T> cmp) {
// assert n > 0;
int half = n >>> 1; // 最後一個非葉子結點下標(由於size已經減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;
}
複製代碼
每次彈出隊首元素,都要進行堆調整。
poll方法能夠看出是remove方法的特例,即固定刪除第一個元素。
public boolean remove(Object o) {
int i = indexOf(o); // 找到待刪除元素位置
if (i == -1)
return false;
else {
removeAt(i); // 刪除指定位置元素
return true;
}
}
複製代碼
調用了removeAt
方法:
E removeAt(int i) {
// assert i >= 0 && i < size;
final Object[] es = queue;
modCount++;
int s = --size; // size已經減1
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; // 正常刪除時返回null
}
複製代碼
java.util.concurrent.PriorityBlockingQueue
;iterator()
遍歷時,不保證輸出的序列是有序的,其實遍歷的是存儲數組。