在業務場景中,處理一個任務隊列,可能須要依照某種優先級順序,這時,Java中的PriorityQueue(優先隊列)即可以派上用場。優先隊列的原理與堆排序密不可分,能夠參考我以前的一篇博客:html
堆排序總結與實現數組
PriorityQueue中維護一個Queue[]數組,在邏輯上把它理解成一個小根堆或大根堆,即一個徹底二叉樹,每個三元組中父節點小於兩個孩子結點(小根堆,若是是大於則是大根堆)。本博客以小根堆來進行說明,由於PriorityQueue默認實現小根堆,即小的數先出隊,固然也能夠自定義Comparator實現大根堆。oop
poll()方法以下:code
public E poll() { if (size == 0) return null; int s = --size; modCount++; E result = (E) queue[0]; E x = (E) queue[s]; queue[s] = null; if (s != 0) siftDown(0, x); return result; }
能夠看到,隊首元素 queue[0] 出隊,隊尾的元素 queue[s] 進入 siftDown(0, x) 方法進行堆調整。siftDown方法以下:htm
private void siftDown(int k, E x) { if (comparator != null) siftDownUsingComparator(k, x); else siftDownComparable(k, x); } //k爲開始遍歷的位置,x爲須要插入的值 @SuppressWarnings("unchecked") private void siftDownComparable(int k, E x) { Comparable<? super E> key = (Comparable<? super E>)x; int half = size >>> 1; // loop while a non-leaf // 只須要遍歷到數組的一半便可,保證遍歷到最後一個三元組的父節點便可 while (k < half) { int child = (k << 1) + 1; // assume left child is least Object c = queue[child]; int right = child + 1; if (right < size && ((Comparable<? super E>) c).compareTo((E) queue[right]) > 0) c = queue[child = right];//比較左右孩子結點,取最小的那個 if (key.compareTo((E) c) <= 0) break;//找到了key應該放入的位置 queue[k] = c; k = child; } queue[k] = key; } @SuppressWarnings("unchecked") private void siftDownUsingComparator(int k, E x) { int half = size >>> 1; while (k < half) { int child = (k << 1) + 1; Object c = queue[child]; int right = child + 1; if (right < size && comparator.compare((E) c, (E) queue[right]) > 0) c = queue[child = right]; if (comparator.compare(x, (E) c) <= 0) break; queue[k] = c; k = child; } queue[k] = x; }
能夠看到,這與堆排序中的堆調整一模一樣。blog
offer方法以下所示:排序
public boolean offer(E e) { if (e == null) throw new NullPointerException(); modCount++; int i = size; if (i >= queue.length) grow(i + 1); size = i + 1; if (i == 0) queue[0] = e; else siftUp(i, e); return true; }
一樣,其核心在於 siftUp(i, e) 方法。以下所示:隊列
private void siftUp(int k, E x) { if (comparator != null) siftUpUsingComparator(k, x); else siftUpComparable(k, x); } @SuppressWarnings("unchecked") private void siftUpComparable(int k, E x) { Comparable<? super E> key = (Comparable<? super E>) x; while (k > 0) { int parent = (k - 1) >>> 1;//結點父節點的下標 Object e = queue[parent]; if (key.compareTo((E) e) >= 0) break;//若是結點值大於父節點,則能夠放置在該三元組下 queue[k] = e;//向子節點賦值父節點的值,不用擔憂某些值被覆蓋,由於初始k等於size k = parent; } queue[k] = key;//最後在待插入位置賦key的值 } @SuppressWarnings("unchecked") private void siftUpUsingComparator(int k, E x) { while (k > 0) { int parent = (k - 1) >>> 1; Object e = queue[parent]; if (comparator.compare(x, (E) e) >= 0) break; queue[k] = e; k = parent; } queue[k] = x; }
此方法,是一個不斷從父節點往子節點賦值的過程,直到找到適合放置插入結點值的位置。element
removeAt 方法以下所示:rem
private E removeAt(int i) { // assert i >= 0 && i < size; modCount++; int s = --size; if (s == i) // removed last element queue[i] = null; else { E moved = (E) queue[s]; queue[s] = null; siftDown(i, moved); if (queue[i] == moved) { siftUp(i, moved); if (queue[i] != moved) return moved; } } return null; }
移除下標爲i的元素,至關於以 i 爲根節點的徹底二叉樹的出隊,因而執行 siftDown 方法調整最後一個元素 moved 的位置,即將該堆調整爲小根堆。調整完以後,若是 moved 沒有來到 i 的位置,說明 i 以上的堆結構必定符合規則;若是 moved 被調整到 i 位置,i上面的父節點有可能比 moved大,因此須要 siftUp(i, moved) 方法從 i 位置向上調整,調整爲小根堆,完畢。
其實無論是 siftUp 方法仍是 siftDown 方法,都是利用了徹底二叉樹的性質,經過父節點與孩子結點之間的快速訪問來實現的。