分析 Queue 和 Priority Queue 的源碼

Queue

首先簡單認識下Queue,查看API定義了6個方法,歸納爲新增、刪除、查詢三種不同操作,每種操作有2個不同的實現,基於JDK 1.8
api接口如下:

新增接口

  • boolean add(E e)
  • boolean offer(E e)

查詢:

  • E element()
  • E peek()

刪除:

  • E remove()
  • E poll()

每種操作有不同的實現,他們區別是什麼?實戰操作

public class App 
{
    public static void main( String[] args ) {

        //addAndOffer();
        //removeAndPoll();
        elementAndPeek();
    }
    

    /**
     * element 和 peek 操作區別
     */
    private static void elementAndPeek() {
        // 設置一個固定長度爲2的隊列
        Queue<String> queue = new ArrayBlockingQueue<String>(2);
        try {
            System.out.println(queue.element()); //java.util.NoSuchElementException
        }catch (Exception e){
            e.printStackTrace();
        }

        System.out.println(queue.poll()); //null

    }


    /**
     * remove 和 poll 操作區別
     */
    private static void removeAndPoll() {
        // 設置一個固定長度爲2的隊列
        Queue<String> queue = new ArrayBlockingQueue<String>(2);
        try {
            queue.add("a");
            queue.add("b");
            System.out.println(queue); // [a, b]
            System.out.println(queue.remove()); // a
            System.out.println(queue.remove()); // b
            System.out.println(queue.remove()); // NoSuchElementException
        }catch (Exception e){
            e.printStackTrace();
        }finally {
            queue.clear();
        }


        queue.add("a");
        queue.add("b");
        System.out.println(queue);// [a, b]
        System.out.println(queue.poll()); // a
        System.out.println(queue.poll()); // b
        System.out.println(queue.poll()); // null



    }


    /**
     * add 和 offer 操作區別
     */
    private static void addAndOffer() {
        // 設置一個固定長度爲2的隊列
        Queue<String> queue = new ArrayBlockingQueue<String>(2);
        try {
            System.out.println(queue.add("a")); // true
            System.out.println(queue.add("b")); // true
            System.out.println(queue.add("c")); // java.lang.IllegalStateException: Queue full
        }catch (Exception e){
            e.printStackTrace();
        }finally {
            queue.clear();
        }

        System.out.println(queue.offer("a")); // true
        System.out.println(queue.offer("b")); // true
        System.out.println(queue.offer("c")); // false
    }
}

通過上面演示可以看出,add、remove、element三個方法在操作過程中,如果訪問超過了容器的固定存儲範圍,那麼會報異常,而另一組offer、poll、peek則會給出一種特殊的返回值,不會報錯。

查看Queue源碼,發現繼承了Collection,從這裏也可以初步知道Queue實際是一個集合,對應的api也是針對集合的一個擴展

public interface Queue<E> extends Collection<E> {

    boolean add(E e);
    boolean offer(E e);

    E remove();
    E poll();

    E element();
    E peek();
}

PriorityQueue

PriorityQueue是Queue的一種實現,而Queue是一種規範,通過Queue中api簡單操作,初步瞭解PriorityQueue

public class PriorityQueueTest {
    public static void main(String[] args) {
        PriorityQueue<Integer> queue = new PriorityQueue<Integer>();
        System.out.println("入隊前數據:[5, 3, 2, 4, 7]");
        queue.add(5);
        System.out.println("入隊第一個數據後的隊列:" + queue);
        queue.add(3);
        System.out.println("入隊第二個數據後的隊列:" + queue);
        queue.add(2);
        System.out.println("入隊第三個數據後的隊列:" + queue);
        queue.add(4);
        System.out.println("入隊第四個數據後的隊列:" + queue);
        queue.add(7);
        System.out.println("全部入隊後的隊列:" + queue);


        System.out.println("移除第一個數據: " + queue.remove());
        System.out.println("移除第一個數據後的隊列:" + queue);

        System.out.println("移除第二個數據: " + queue.remove());
        System.out.println("移除第二個數據後的隊列:" + queue);

        System.out.println("移除第三個數據: " + queue.remove());
        System.out.println("移除第三個數據後的隊列:" + queue);

        System.out.println("移除第四個數據: " + queue.remove());
        System.out.println("移除第四個數據後的隊列:" + queue);

        System.out.println("移除最後個數據: " + queue.remove());
        System.out.println("移除最後個數據後的隊列:" + queue);
    }
}
入隊前數據:[5, 3, 2, 4, 7]
入隊第一個數據後的隊列:[5]
入隊第二個數據後的隊列:[3, 5]
入隊第三個數據後的隊列:[2, 5, 3]
入隊第四個數據後的隊列:[2, 4, 3, 5]
全部入隊後的隊列:[2, 4, 3, 5, 7]
移除第一個數據: 2
移除第一個數據後的隊列:[3, 4, 7, 5]
移除第二個數據: 3
移除第二個數據後的隊列:[4, 5, 7]
移除第三個數據: 4
移除第三個數據後的隊列:[5, 7]
移除第四個數據: 5
移除第四個數據後的隊列:[7]
移除最後個數據: 7
移除最後個數據後的隊列:[]

根據打印結果,入隊或出隊後,會把每次隊列中最小數放在隊首。而且出隊的時候數據依次增大。這就是PriorityQueue一個特點:根據隊列中數據以某種優先級進行將數據出隊
如何實現呢?查看源碼

// 查看入隊源碼
// queue實際是一個數組
transient Object[] queue;

public boolean add(E e) {
    return offer(e);
}

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

private void siftUp(int k, E x) {
    if (comparator != null)// 自定義比較器不爲空
        siftUpUsingComparator(k, x); // 使用自定義比較器的進行入隊
    else
        siftUpComparable(k, x); // 使用默認比較器的進行入隊
}

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

對入隊源碼的分析知道,每次入隊操作都會根據比較器進行判斷,選擇合適元素放在隊首,所以添加的隊列數據必須實現Comparator接口或者實例化Priority Queue 提供一個自定義比較器
在看看出隊操作。同時add的內部實現是通過offer實現的,

public E remove() {
    E x = poll(); // 出隊
    if (x != null)
        return x;
    else
        throw new NoSuchElementException();
}

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

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一些特點:

  • 1、入隊元素必須實現Comparator比較器接口,或者用戶自己提供比較器,否則無法使用
  • 2、PriorityQueue通過數組實現,數組長度雖然固定,但可以擴容,
  • 3、因爲可以擴容add不會存在容器長度問題報異常,而remove和element仍然存在此問題
  • 4、PriorityQueue是非線程安全的,新增、刪除操作沒有加鎖操作

PriorityQueue應用場景

  • 處理很多任務且任務有不同的執行級別
發表評論
所有評論
還沒有人評論,想成為第一個評論的人麼? 請在上方評論欄輸入並且點擊發布.
相關文章