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.htmljavaversion: java12node
在Java中,Queue
是一個抽象的接口,定義了隊列(特性:FIFO)最基本的操做,在其之下,又分別了定義其它的接口繼承了Queue
:api
BlockingQueue
,是阻塞的雙端隊列
BlockingDeque
,在其基礎上又增長了保證生產者阻塞直到元素被某個線程消費的行爲,具體實現查看
LinkedTransferQueue
以上的BlockingQueue
、BlockingDeque
、TransferQueue
用於多線程,這裏不過多介紹。咱們主要看Deque
的實現LinkedList
,並以實現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
這樣的實現來講,首先要定義出大體的結構: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
的數據結構來講,它定義了first
和last
兩個首尾指針,指向它首尾位置的元素。其中Node
是每一個元素在這個數據結構中的定義,除了自己的內容以外,還定義了prev
、next
指向先後兩個節點。ide
不管是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()
真實實現都在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()
都是直接返回LinkedList
定義的first
元素,無非斷定拋異常或者返回null而已。
這裏很少贅述。
docs: https://docs.oracle.com/en/java/javase/12/docs/api/java.base/java/util/PriorityQueue.html
version: java12
PriorityQueue
也是Queue
的一個具體實現,是基於優先級堆的無界優先隊列,其功能主要是實現了元素的有序性。若是想要使用線程安全的優先隊列,則應該使用PriorityBlockingQueue
。
PriorityQueue
的排序是天然順序排序或者是實現Comparator
,這取決於構造的時候用哪一個構造函數,插入的元素不能爲空,不能不可比較。
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
中使用。
咱們先來看下插入元素。
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()
方法爲例:
int parent = (k - 1) >>> 1;
其實就是使用公式1
計算parent的值;獲取元素就比較簡單,獲取最小值的那個元素,本質上就是獲取數組的最小值。