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的值;获取元素就比较简单,获取最小值的那个元素,本质上就是获取数组的最小值。