JDK源碼走讀(2):容器之PriorityQueue

Java的容器分成四個系列:Set, List, Queue,Map,除Map外,其餘三個都實現了Collection接口,List和Queue實現順序存儲,Map實現了K-V對。

本章分析其中實現較爲簡單的Queue系列

一、類實現/繼承體系結構

爲了對整個Queue實現/繼承體系有個全貌,先將體系結構圖畫出來:

二、關鍵數據成員

(1)存儲結構

PriorityQueue內部使用平衡二叉樹(小根堆)保存元素,底層採用的數據結構是數組:

transientObject[] queue;

利用數組保存二叉樹,那麼某個節點queue[n]的左子樹和右子樹分別是queue[2*n+1] 和queue[2*(n+1)]。

隊列queue的默認初始化大小(DEFAULT_INITIAL_CAPACITY)是11。

(2)比較運算符

優先級隊列要麼根據比較運算符進行排序,當運算符爲null時,則根據元素的自然順序進行排序,最小值是queue[0]。

在PriorityQueue中,有一個成員變量用於記錄採取的比較運算符:

private final Comparator<? super E> comparator;

PriorityQueue要求外部傳入的Comparator比較器的類型必須是當前正在構造類的本身或者是其父類、祖父類。。。總之,在繼承層次上,大於等於它自身

爲什麼這麼要求呢?

我琢磨着原因應該是這樣:

一個類的比較,最終要落到對其數據成員的操作,得到比較結果,子類是繼承了父類的數據成員(也許是部分,除掉一些在父類中的private成員,後面再討論),也就是說,利用父類的Comparator進行比較時,對子類也是適用的,但反過來,子類可能定義了一些新的數據成員,是父類中沒有的,如果子類的Comparator在compare函數中使用這些新的數據成員比較,那麼對父類是不適用的,所以,子類可以使用父類的Comparator,反過來不行。

對於父類使用私有數據成員實現compare函數,分兩種情況:

一種子類可以通過構造函數,對父類的私有成員賦值,如果構造函數中有對私有成員初始化的參數,那麼這種情況也會子類的比較起作用;

另一種情況,子類本身無法使用父類的私有數據成員,又沒有辦法改變它,那麼父類的comparator對子類的排序不起作用。

(3)修改次數

transientint modCount = 0;

數據成員modCount用於記錄queue隊列被修改的次數

(4)

三、構造函數

PriorityQueue提供了多種形式的構造函數,在此不一一列舉,就參數來說,主要包括,隊列(queue)初始化大小(initialCapacity)值、比較運算符、其他集合(包括PriorityQueue或者別的類型的集合,比如Set)

這裏要講下以下幾個構造函數:

public MyPriorityQueue(Collection<?extends E> c)

       <? extends E>表示c中的元素(?)只能是類型E的子類,這樣其實將一個子類對象賦值給了一個父類對象,父類對象指向了一個子類對象,即多態,比如:

     class Fruit {

     }

     class Apple extends Fruit {

     }

     List<Apple>appleL= newArrayList<Apple>();

    appleL.add(new Apple("red"));

    appleL.add(new Apple("green"));     

Queue<Fruit>fruitQ= newPriorityQueue<Fruit>(appleL);

反過來就會出錯了,如果用一個存放Fruit的List作爲PriorityQueue的構造函數的參數就會出錯了。

注意,上面是簡化的寫法,代碼是無法編譯通過的,因爲PriorityQueue在存入對象時涉及到對象之間的比較,那就要去存入的對象之間可以進行比較,一種是傳遞了Comparator,實現對象的比較;另一種是類實現Comparable接口的比較函數compareTo,有些類本身支持比較,比如String,Integer,但對於自定義的類,那就要求實現者自己實現compareTo,當調用PriorityQueue構造函數時沒有傳遞Comparator,PriorityQueue在比較元素的時候就調用類的compareTo進行比較,如果二者都沒有提供,那就會拋異常了。下面是實現了compareTo的例子,如:

class Fruit implements Comparable<Fruit> {

    protected String name;

    public Fruit(String name) {

        this.name = name;

    }

    public String getName() {

        returnname;

    }
   @Override
    publicint compareTo(Fruit o) {
        returnthis.name.compareTo(o.getName());
    }
}

class Apple extends Fruit {
    public Apple(String name) {
        super(name);
    }
}

四、堆操作

PriorityQueue底層使用堆進行存儲,後面要講的隊列的增刪改查很多時候涉及到對堆的操作。堆的操作主要包括兩個:

siftUp

siftDown

PriorityQueue實現了標準的heap上浮(sift up)和下沉(sift down)操作,只是在進行元素比較時,不是直接比較元素大小,而是使用用戶傳入的comparator或者元素所屬類的compareTo成員函數來比較大小。

   private voidsiftUp(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 siftUpUsingComparator(int k, Ex) {

        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;
    }

    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;
    }

    @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;

    }

五、增

在往PriorityQueue中添加元素(add/offer)PriorityQueue在隊列滿的時候,自動實現擴容。擴容的操作是privatevoid grow(int minCapacity)實現的,

    private void grow(int minCapacity) {

        int oldCapacity = queue.length;

        // Double size if small; else grow by50%

        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);

}

add的實現就是調用offer,首先檢查參數是否合法,PriorityQueue中不允許保存null元素;接下來檢查隊列是否已滿,如果滿了,執行grow,擴容;然後將元素放入隊列數組,執行堆操作siftUp,當數組中不止一個元素時。

六、刪

boolean remove(Object o):刪除對象o,首先利用indexOf(Object o)找到o的index,然後調用removeAt(private E removeAt(int i)):

   privateE 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;
}

       如果不是最後一個對象,刪除對象後,需要執行siftDown/siftUp操作。

       同樣的,poll操作是刪除堆的根元素,即隊列的queue[0]元素,

public Epoll() {

        if (size == 0)

            return null;

        int s = --size;

        modCount++;

        E result = (E) queue[0];

        E x = (E) queue[s];

        queue[s] = null;

//如果s==0,則說明此次刪除了隊列裏的最後一個元素,

//刪除後,隊列爲空,無需執行siftDown

        if (s != 0)  

            siftDown(0, x);

        return result;

    }

七、查

peek():返回隊列queue中index=0的元素,queue[0];如果爲空,則返回null。
contains(Object o):
public booleancontains(Object o) {
    return indexOf(o) != -1;
}

總結:

(1)   PriorityQueue實現可以作爲學習堆數據結構的範例;

(2)   PriorityQueue還有一個內部類private finalclass Itr,實現了迭代器;

發表評論
所有評論
還沒有人評論,想成為第一個評論的人麼? 請在上方評論欄輸入並且點擊發布.
相關文章