原本第二篇想解析一下LinkedList,不過掃了一下源碼後,以爲LinkedList的實現比較簡單,沒有什麼意思,因而移步PriorityQueue。算法
PriorityQueue經過數組實現了一個堆數據結構(至關於一棵徹底二叉樹),元素的優先級能夠經過一個Comparator來計算,若是不指定Comparator,那麼元素類型應該實現Comparable接口。最終compare得出的最小元素,放在堆的根部。api
public class PriorityQueue<E> extends AbstractQueue<E> { transient Object[] queue; // non-private to simplify nested class access private int size = 0; private final Comparator<? super E> comparator; transient int modCount = 0; // non-private to simplify nested class access }
PriorityQueue的成員變量和ArrayList高度相似:數組
PriorityQueue最重要的部分就是維護堆的幾個方法,它基本實現了《算法導論》介紹的堆算法。數據結構
一、插入元素siftDown
假定k位置的左右子樹都是堆,siftDown方法把元素x插入位置k,而後對這個子堆進行調整,保證以k爲根的子樹也是堆。視comparator是否存在,siftDown使用siftDownUsingComparator或siftDownComparable,它們只是比較元素的方式不同,結構是徹底一致的,這裏就只解讀siftDownComparable。oop
private void siftDown(int k, E x) { if (comparator != null) siftDownUsingComparator(k, x); else siftDownComparable(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; queue[k] = c; k = child; } queue[k] = key; }
二、插入元素siftUpthis
往k位置插入值x,siftUp採用的方式是往樹根方向移動,將祖先節點compare值比較大的往下交換。
siftup的用途要少一點。code
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 = parent; } queue[k] = key; }
三、建堆對象
@SuppressWarnings("unchecked") private void heapify() { for (int i = (size >>> 1) - 1; i >= 0; i--) siftDown(i, (E) queue[i]); }
從中間索引位置開始(日後的都是葉節點),往樹根方向遍歷,對每一個位置調用siftDown。索引
左右子樹都是子堆
的條件,siftDown使得,以i爲樹根的節點也是子堆;如今能夠來看看queue最多見的兩個操做:offer和poll。接口
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; }
offer先把一個對象放入隊列的末尾,前面的空間檢查及增加和ArrayList極爲相似。
i就是插入位置,若是i==0,queue是空的,插入就完事了;不然經過siftUp來插入,由於i是葉節點,因此能夠認爲i子樹是一個子堆,siftup保證e會往樹根方向,找到一個合適的位置,使整棵樹保持了堆的特性。
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; }
poll取出樹根元素做爲返回值,而後將最後一個元素取出來,經過siftDown插入到樹根位置,前面講過siftDown的性質,這個操做使得整棵樹仍然保持堆特性。
public boolean remove(Object o) { int i = indexOf(o); if (i == -1) return false; else { removeAt(i); return true; } } 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; }
這個private的removeAt操做比較有意思,它執行的操做是刪除第i個節點(存儲空間的第i個,而不是優先級的第i個,這塊容易引發人的誤解,因此沒有相似的public方法)。
這個返回值也是出人意料,不是返回刪除的元素,而是在保持堆特性的過程當中,若是有尾部元素被移動到i以前的位置,就返回它。這純粹是爲了幫助PriorityQueue的迭代器實現,下一節立刻解釋。
首先要明確一點,PriorityQueue的迭代器並不按優先級順序來遍歷元素,主要就是按存儲順序來遍歷,先看迭代器的成員
private final class Itr implements Iterator<E> { private int cursor = 0; private int lastRet = -1; private int expectedModCount = modCount; private ArrayDeque<E> forgetMeNot = null; private E lastRetElt = null; }
cursor、lastRet、expectedModCount的做用和ArrayList的迭代器徹底一致;可是多出來的forgetMeNot和lastRetElt讓人有點莫民奇妙。
再看看remove方法的實現:
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; }
若是lastRet有效,那麼調用PriorityQueued.removeAt(lastRet)來刪除元素,經過上一節咱們知道,removeAt方法可能致使某個元素從末尾被移動到lastRet前面,這樣的話,迭代器就會丟失這個元素。爲了解決這個問題,迭代器把這個元素放到了一個臨時ArrayDeque裏面。
這樣若是lastRet沒有指向有效的元素,那麼有可能正在遍歷ArrayDeque裏面的元素,此時經過lastRetElt來指向。
再看next方法就很容易明白了
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(); }
先順着cursor遍歷,再把forgetMeNot裏面的元素遍歷一遍。