【源码】Queue AbstractQueue PriorityQueue 浅析
前言
一般我们认为的 Queue 是队列,其遵循 FIFO(先进先出)的原则,此时 head
元素即为我们要取出的下一个元素,而插入的下一个元素即 tail
。而对于 LIFO (后进先出)比如 栈,其下一个取出的元素依然是 head
,插入的元素则就不是 tail
了。
Queue
public interface Queue<E> extends Collection<E> {
// 添加元素,失败抛异常
boolean add(E e);
// 添加元素,失败返回 false
boolean offer(E e);
// 移除元素,失败抛异常
E remove();
// 移除元素,失败返回 false
E poll();
// 检索第一个元素,失败抛异常
E element();
// 检索第一个元素,失败返回 false
E peek();
}
Queue 拓展了 Collection,提供的操作方法分类如下
Queue | 操作失败抛出异常 | 操作失败返回指定值(true false 等) |
---|---|---|
插入 | add(e) | offer(e) |
移除 | remove() | poll() |
检索 | element() | peek() |
AbstractQueue
public abstract class AbstractQueue<E>
extends AbstractCollection<E>
implements Queue<E> {
protected AbstractQueue() {
}
// 交给 offer(子类实现),失败则抛异常
public boolean add(E e) {
if (offer(e))
return true;
else
throw new IllegalStateException("Queue full");
}
// 交给 poll(子类实现),失败则抛异常
public E remove() {
E x = poll();
if (x != null)
return x;
else
throw new NoSuchElementException();
}
// 交给 peek(子类实现),失败则抛异常
public E element() {
E x = peek();
if (x != null)
return x;
else
throw new NoSuchElementException();
}
// 无限 poll
public void clear() {
while (poll() != null)
;
}
// 遍历 add
public boolean addAll(Collection<? extends E> c) {
if (c == null)
throw new NullPointerException();
if (c == this)
throw new IllegalArgumentException();
boolean modified = false;
for (E e : c)
if (add(e))
modified = true;
return modified;
}
}
AbstractQueue ,Queue 的抽象实现,相当于搭了个框架,将 失败后抛异常 的操作都委托给 子类方法 实现,我们看一个具体的实现类 PriorityQueue
PriorityQueue
PriorityQueue 是一个 无界的、有优先级 的队列
属性、构造方法
// 默认数组大小 11
private static final int DEFAULT_INITIAL_CAPACITY = 11;
// 维护元素的无界数组
transient Object[] queue;
// 元素数量
int size;
// 维护优先级的 Comparator
private final Comparator<? super E> comparator;
// 记录操作次数
transient int modCount;
public PriorityQueue() {
this(DEFAULT_INITIAL_CAPACITY, null);
}
public PriorityQueue(int initialCapacity) {
this(initialCapacity, null);
}
// 指定 comparator
public PriorityQueue(Comparator<? super E> comparator) {
this(DEFAULT_INITIAL_CAPACITY, comparator);
}
// 指定初始容量 initialCapacity 和 comparator
public PriorityQueue(int initialCapacity,
Comparator<? super E> comparator) {
if (initialCapacity < 1)
throw new IllegalArgumentException();
this.queue = new Object[initialCapacity];
this.comparator = comparator;
}
虽然称为无界队列,但是也需要定义 初始容量(类似 ArrayList),且会在元素达到 阈值 时进行 扩容,允许自定义 初始化容量 和 Comparator
add(E e)
// 因为无界,因此复写了 AbstractQueue 的 add 方法
public boolean add(E e) {
return offer(e);
}
public boolean offer(E e) {
// 元素不能为 null
if (e == null)
throw new NullPointerException();
// 操作次数
modCount++;
int i = size;
// 元素是否超过容量
if (i >= queue.length)
// 扩容
grow(i + 1);
// 元素插入
siftUp(i, e);
// 长度递增
size = i + 1;
return true;
}
---------------- 扩容 -----------------
private void grow(int minCapacity) {
int oldCapacity = queue.length;
// 小于 64 时以 2 为单位,之后翻倍
int newCapacity = oldCapacity + ((oldCapacity < 64) ?
(oldCapacity + 2) :
(oldCapacity >> 1));
if (newCapacity - MAX_ARRAY_SIZE > 0)
newCapacity = hugeCapacity(minCapacity);
queue = Arrays.copyOf(queue, newCapacity);
}
siftUp
private void siftUp(int k, E x) {
if (comparator != null)
siftUpUsingComparator(k, x, queue, comparator);
else
siftUpComparable(k, x, queue);
}
private static <T> void siftUpComparable(int k, T x, Object[] es) {
Comparable<? super T> key = (Comparable<? super T>) x;
// 遍历折中
// 如果当前元素 x 大于 折中元素 e,则直接插入数组末尾
// 反之,则继续向前折中比对,最后交换
// 即保证了每次插入的最小的元素放在数组首位
while (k > 0) {
int parent = (k - 1) >>> 1;
Object e = es[parent];
if (key.compareTo((T) e) >= 0)
break;
es[k] = e;
k = parent;
}
es[k] = key;
}
// 同上,基于用户自定义的 Comparator
private static <T> void siftUpUsingComparator(
int k, T x, Object[] es, Comparator<? super T> cmp) {
while (k > 0) {
int parent = (k - 1) >>> 1;
Object e = es[parent];
if (cmp.compare(x, (T) e) >= 0)
break;
es[k] = e;
k = parent;
}
es[k] = x;
}
插入元素时会基于 Comparator 优先级调整,保证最小的元素列在数组首位
注意此时并未保证元素完全有序,比如插入 3 2 1 6 7
时对应数组为 1 3 2 6 7
,此时以中间点一分为二,前半段基本保证有序且小于中间点,但也不排除前后相邻为顺序颠倒,比如上例中的 2
和 3
,具体原因可以自己模拟体会一下
remove()
--------- from AbstractQueue ----------
public E remove() {
E x = poll();
if (x != null)
return x;
else
throw new NoSuchElementException();
}
public E poll() {
final Object[] es;
final E result;
// 返回的是数组头元素,插入时已将最小元素排在数组头
if ((result = (E) ((es = queue)[0])) != null) {
// 操作数累加
modCount++;
final int n;
final E x = (E) es[(n = --size)];
es[n] = null;
if (n > 0) {
final Comparator<? super E> cmp;
if ((cmp = comparator) == null)
// 调整数组头为最小的元素
siftDownComparable(0, x, es, n);
else
// 使用自定义的 comparator
siftDownUsingComparator(0, x, es, n, cmp);
}
}
return result;
}
private static <T> void siftDownComparable(int k, T x, Object[] es, int n) {
Comparable<? super T> key = (Comparable<? super T>)x;
// 折中
int half = n >>> 1;
while (k < half) {
// 从第二位开始调整
int child = (k << 1) + 1; // assume left child is least
Object c = es[child];
// 此处就是解决之前提到的可能出现 临近位 次序颠倒情况
int right = child + 1;
if (right < n &&
((Comparable<? super T>) c).compareTo((T) es[right]) > 0)
c = es[child = right];
if (key.compareTo((T) c) <= 0)
break;
// 下一轮比对
es[k] = c;
k = child;
}
// 交换顺序
es[k] = key;
}
// 同上,使用自定义的 Comparator
private static <T> void siftDownUsingComparator(
int k, T x, Object[] es, int n, Comparator<? super T> cmp) {
int half = n >>> 1;
while (k < half) {
int child = (k << 1) + 1;
Object c = es[child];
int right = child + 1;
if (right < n && cmp.compare((T) c, (T) es[right]) > 0)
c = es[child = right];
if (cmp.compare(x, (T) c) <= 0)
break;
es[k] = c;
k = child;
}
es[k] = x;
}
返回已排在数组头的 最小元素,然后调整数组再次将 最小元素 排在数组头,等待下一次被获取
element()
--------- from AbstractQueue ----------
public E element() {
E x = peek();
if (x != null)
return x;
else
throw new NoSuchElementException();
}
// 返回数组头
public E peek() {
return (E) queue[0];
}
调用 peek()
返回数组头元素
其他方法
还有很多其他方法,比如 remove(E e)
forEach(Consumer<? super E> action)
等等就不一一列举了
demo
public class TestPriorityQueue {
class Node {
int id;
String name;
public Node(int id, String name) {
this.id = id;
this.name = name;
}
public int getId() {
return id;
}
public void setId(int id) {
this.id = id;
}
public String getName() {
return name;
}
public void setName(String name) {
this.name = name;
}
@Override
public String toString() {
return "Node{" +
"id=" + id +
", name='" + name + '\'' +
'}';
}
}
@Test
public void test1() {
PriorityQueue priorityQueue = new PriorityQueue(
new Comparator<Node>() {
@Override
public int compare(Node o1, Node o2) {
return o1.id - o2.id;
}
}
);
priorityQueue.add(new Node(3, "3"));
priorityQueue.add(new Node(4, "4"));
priorityQueue.add(new Node(1, "1"));
priorityQueue.add(new Node(6, "6"));
priorityQueue.add(new Node(2, "2"));
while (!priorityQueue.isEmpty()) {
System.out.println(priorityQueue.remove());
}
}
}
结果:
Node{id=1, name='1'}
Node{id=2, name='2'}
Node{id=3, name='3'}
Node{id=4, name='4'}
Node{id=6, name='6'}
总结
本章节介绍了 Queue 接口及其抽象实现类 AbstractQueue ,最后介绍了基于 优先级 的具体实现类 PriorityQueue ,其在元素 插入 和 取出 时的同时,对数组进行调整,以保证元素的 优先级,优先级规则由可自定义的 Comparator 实现