Queue和Priority Queue源碼解讀

Queue

Queue docs: https://docs.oracle.com/en/java/javase/12/docs/api/java.base/java/util/Queue.htmlhtml

LinkedList docs: https://docs.oracle.com/en/java/javase/12/docs/api/java.base/java/util/LinkedList.htmljava

version: java12node

在Java中,Queue是一個抽象的接口,定義了隊列(特性:FIFO)最基本的操做,在其之下,又分別了定義其它的接口繼承了Queueapi

  • BlockingQueue :阻塞隊列
  • BlockingDeque :繼承了 BlockingQueue,是阻塞的雙端隊列
  • Deque :雙端隊列
  • TransferQueue :繼承了 BlockingDeque,在其基礎上又增長了保證生產者阻塞直到元素被某個線程消費的行爲,具體實現查看 LinkedTransferQueue

以上的BlockingQueueBlockingDequeTransferQueue用於多線程,這裏不過多介紹。咱們主要看Deque的實現LinkedList,並以實現Queue的視角描述。數組

Queue

Queue定義了幾個基本操做以下:安全

會拋出異常 返回特殊值(通常爲null)
插入 add(e) offer(e)
刪除 remove() poll()
取出元素 element() peek()

固然,Java內經常使用的鏈表數據結構LinkedList是實現了Deque接口。Deque接口是在Queue的基礎上又擴展了一些函數。數據結構

會拋出異常 返回特殊值(通常爲null)
插入頭 addFirst(e) offerFirst(e)
插入尾 addLast(e) offerLast(e)
刪除頭 removeFirst() pollFirst()
刪除尾 removeLast() pollLast()
取出頭 elementFirst() peekFirst()
取出尾 elementLast() peekLast()

Deque的操做定義除了有FIFO的特性以外,它也具有棧的特徵——LIFO多線程

棧方法(Stack) 等效的Deque方法
push(e) addFirst(e)
pop() removeFirst()
peek() getFirst()

LinkedList

LinkedList這樣的實現來講,首先要定義出大體的結構:oracle

public class LinkedList<E>
    extends AbstractSequentialList<E>
    implements List<E>, Deque<E>, Cloneable, java.io.Serializable
{
    transient int size = 0;

    /**
     * Pointer to first node.
     * Invariant: (first == null && last == null) ||
     *            (first.prev == null && first.item != null)
     */
    transient Node<E> first;

    /**
     * Pointer to last node.
     * Invariant: (first == null && last == null) ||
     *            (last.next == null && last.item != null)
     */
    transient Node<E> last;

    private static class Node<E> {
        E item;
        Node<E> next;
        Node<E> prev;

        Node(Node<E> prev, E element, Node<E> next) {
            this.item = element;
            this.next = next;
            this.prev = prev;
        }
    }
}

LinkedList的數據結構來講,它定義了firstlast兩個首尾指針,指向它首尾位置的元素。其中Node是每一個元素在這個數據結構中的定義,除了自己的內容以外,還定義了prevnext指向先後兩個節點。ide

add() 、offer()

不管是add()addLast()方法,最終都是使用的linkLast()方法。
其實現就是取出last節點,實例化一個新的Node,設置新節點的prev,並將新的節點設置爲當前新的last節點。

public class LinkedList<E>{
    /**
     * Links e as last element.
     */
    void linkLast(E e) {
        final Node<E> l = last;
        final Node<E> newNode = new Node<>(l, e, null);
        last = newNode;
        if (l == null)
            first = newNode;
        else
            l.next = newNode;
        size++;
        modCount++;
    }
}
remove()、poll()

remove()poll()真實實現都在unlinkFirst()方法中。
本質上就是取出第一個元素,置空next,再取出第二個元素,將第二個元素的prev置空,這樣就完成了頭元素的刪除操做。

public class LinkedList<E>{

    public E removeFirst() {
        final Node<E> f = first;
        if (f == null)
            throw new NoSuchElementException();
        return unlinkFirst(f);
    }

    /**
     * Unlinks non-null first node f.
     */
    private E unlinkFirst(Node<E> f) {
        // assert f == first && f != null;
        final E element = f.item;
        final Node<E> next = f.next;
        f.item = null;
        f.next = null; // help GC
        first = next;
        if (next == null)
            last = null;
        else
            next.prev = null;
        size--;
        modCount++;
        return element;
    }

}

element()、peek()

element()peek()都是直接返回LinkedList定義的first元素,無非斷定拋異常或者返回null而已。
這裏很少贅述。

PriorityQueue

docs: https://docs.oracle.com/en/java/javase/12/docs/api/java.base/java/util/PriorityQueue.html

version: java12

PriorityQueue也是Queue的一個具體實現,是基於優先級堆的無界優先隊列,其功能主要是實現了元素的有序性。若是想要使用線程安全的優先隊列,則應該使用PriorityBlockingQueue
PriorityQueue的排序是天然順序排序或者是實現Comparator,這取決於構造的時候用哪一個構造函數,插入的元素不能爲空,不能不可比較。

PriorityQueue Define

public class PriorityQueue<E> extends AbstractQueue<E>
    implements java.io.Serializable {
    
    private static final int DEFAULT_INITIAL_CAPACITY = 11;
    
    /**
     * Priority queue represented as a balanced binary heap: the two
     * children of queue[n] are queue[2*n+1] and queue[2*(n+1)].  The
     * priority queue is ordered by comparator, or by the elements'
     * natural ordering, if comparator is null: For each node n in the
     * heap and each descendant d of n, n <= d.  The element with the
     * lowest value is in queue[0], assuming the queue is nonempty.
     */
    transient Object[] queue; // non-private to simplify nested class access

    /**
     * The number of elements in the priority queue.
     */
    private int size = 0;

    /**
     * The comparator, or null if priority queue uses elements'
     * natural ordering.
     */
    private final Comparator<? super E> comparator;

    /**
     * The number of times this priority queue has been
     * <i>structurally modified</i>.  See AbstractList for gory details.
     */
    transient int modCount = 0; 
}

PriorityQueue底層是一個數組做爲容器,初始容量爲11。

那爲何是數組呢?
這源自於PriorityQueue數據結構的設計是二叉小頂堆,而它正好能夠經過數組表示。

假如定義的數組數據以下:

Character[] queue = new Character[]{'a','b','C','D','E','F','G'};

那麼數組下標爲零的元素就在堆頂,此時堆頂元素爲'a',那麼'a'左邊的元素就是'b','a'右邊的元素就是'C','b'的左邊元素爲'D',右邊元素爲'E',以此類推,最終構成了小頂堆的結構。
以下圖所示:

咱們基本能夠得出幾個通用的公式:

公式1:父節點在數組中的下標 = (當前節點下標值 - 1) / 2
公式2:左節點的下標 = 當前節點下標值*2 + 1 
公式3:右節點的下標 = 當前節點下標值*2 + 2

這幾個公式會在PriorityQueue中使用。

offer()

咱們先來看下插入元素。

public class PriorityQueue<E>{
    /**
     * Inserts the specified element into this priority queue.
     *
     * @return {@code true}
     */
    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;
    }

    /**
     * Inserts item x at position k, maintaining heap invariant by
     * promoting x up the tree until it is greater than or equal to
     * its parent, or is the root.
     *
     * To simplify and speed up coercions and comparisons. the
     * Comparable and Comparator versions are separated into different
     * methods that are otherwise identical. (Similarly for siftDown.)
     *
     * @param k the position to fill
     * @param x the item to insert
     */
    private void siftUp(int k, E x) {
        if (comparator != null)
            siftUpUsingComparator(k, x);
        else
            siftUpComparable(k, x);
    }
    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;
    }
    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;
    }
}

offer()方法進來時,判斷數組長度是否容量不足,容量不足則經過grow()方法擴容:

private void grow(int minCapacity) {
        int oldCapacity = queue.length;
        // Double size if small; else grow by 50%
        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);
    }

當前數組容量小於64,則新的容量爲原來的容量+2;當前數組容量大於64,則新的容量爲原來的容量基礎上再增長50%,最大不能超過Int的最大值。

核心方法是siftUp()方法,非空隊列每次新增元素都須要從新調整堆。
咱們以siftUpUsingComparator()方法爲例:

  1. int parent = (k - 1) >>> 1; 其實就是使用公式1計算parent的值;
  2. 第一次循環最後一個值與父節點比較,若是是最大值,則不用動,不然這兩個元素位置置換;
  3. 繼續循環,直到跳出循環,或者到堆頂;

peek()

獲取元素就比較簡單,獲取最小值的那個元素,本質上就是獲取數組的最小值。

相關文章
相關標籤/搜索