1. ArrayDeque
ArrayDeque,数组双端队列(具有栈的特点),实现了Deque(Queue)接口,线程不安全。
ArrayDeque内部结构是一个循环数组,它的成员变量如下:
transient Object[] elements;
transient int head;
transient int tail;
private static final int MIN_INITIAL_CAPACITY = 8;
它的扩容操作如下(通过移位或的操作进行放大倍数,使得数组的容量值不必严格满足2的幂):
// 初始化时分配数组大小
private static int calculateSize(int numElements) {
int initialCapacity = MIN_INITIAL_CAPACITY;
// 最佳匹配元素与2的乘积
if (numElements >= initialCapacity) {
initialCapacity = numElements;
initialCapacity |= (initialCapacity >>> 1);
initialCapacity |= (initialCapacity >>> 2);
initialCapacity |= (initialCapacity >>> 4);
initialCapacity |= (initialCapacity >>> 8);
initialCapacity |= (initialCapacity >>> 16);
initialCapacity++;
if (initialCapacity < 0) // Too many elements, must back off
initialCapacity >>>= 1; // Good luck allocating 2 ^ 30 elements
}
return initialCapacity;
}
// 新增元素时扩容
public void addFirst(E e) {
if (e == null)
throw new NullPointerException();
elements[head = (head - 1) & (elements.length - 1)] = e;
if (head == tail)
doubleCapacity();
}
private void doubleCapacity() {
assert head == tail;
int p = head;
int n = elements.length;
int r = n - p; // number of elements to the right of p
int newCapacity = n << 1;
if (newCapacity < 0)
throw new IllegalStateException("Sorry, deque too big");
Object[] a = new Object[newCapacity];
System.arraycopy(elements, p, a, 0, r);
System.arraycopy(elements, 0, a, r, p);
elements = a;
head = 0;
tail = n;
}
ArrayDeque的UML类图如下:
2. PriorityQueue
PriorityQueue队列使用的是最小堆结构,可用于排序。
它的成员变量与其中之一的构造方法如下:
public class PriorityQueue<E> extends AbstractQueue<E>
implements java.io.Serializable {
private static final int DEFAULT_INITIAL_CAPACITY = 11;
/**
* 优先队列队列[n]有两个子队列:队列[2*n+1]和队列[2*(n+1)]。
* 优先级队列由比较器排序,如果比较器为空,则由元素的自然顺序排序:
* 对于堆中的每个节点n和n的每个后代d,n<=d。
*/
transient Object[] queue;
private int size = 0;
private final Comparator<? super E> comparator;
// 优先队列已被删除的次数
transient int modCount = 0;
// 构造方法
public PriorityQueue(Collection<? extends E> c) {
if (c instanceof SortedSet<?>) {
SortedSet<? extends E> ss = (SortedSet<? extends E>) c;
this.comparator = (Comparator<? super E>) ss.comparator();
initElementsFromCollection(ss);
}
else if (c instanceof PriorityQueue<?>) {
PriorityQueue<? extends E> pq = (PriorityQueue<? extends E>) c;
this.comparator = (Comparator<? super E>) pq.comparator();
initFromPriorityQueue(pq);
}
else {
this.comparator = null;
initFromCollection(c);
}
}
private void initElementsFromCollection(Collection<? extends E> c) {
Object[] a = c.toArray();
// If c.toArray incorrectly doesn't return Object[], copy it.
if (a.getClass() != Object[].class)
a = Arrays.copyOf(a, a.length, Object[].class);
int len = a.length;
if (len == 1 || this.comparator != null)
for (int i = 0; i < len; i++)
if (a[i] == null)
throw new NullPointerException();
this.queue = a;
this.size = a.length;
}
private void initFromPriorityQueue(PriorityQueue<? extends E> c) {
if (c.getClass() == PriorityQueue.class) {
this.queue = c.toArray();
this.size = c.size();
} else {
initFromCollection(c);
}
}
private void initFromCollection(Collection<? extends E> c) {
initElementsFromCollection(c);
heapify();
}
}
构造方法中需要注意的是,initElementFromCollection 方法本身并不能保证初始化后queue数组元素堆化/排序,所以 initFromCollection 方法在调用完 initElementFromCollection 后进行了堆化heapify;至于传入的集合是SortedSet时,PriorityQueue会继续使用SortedSort中的Comparator对象来排序,无论怎么排序,新创建的PriorityQueue中的数据对象都会符合PriorityQueue对数据对象存储的要求。
扩容操作:
private void grow(int minCapacity) {
int oldCapacity = queue.length;
// 如果原始容量小于64则双倍扩容,否则扩大1.5倍
int newCapacity = oldCapacity + ((oldCapacity < 64) ?
(oldCapacity + 2) :
(oldCapacity >> 1));
if (newCapacity - MAX_ARRAY_SIZE > 0)
newCapacity = hugeCapacity(minCapacity);
queue = Arrays.copyOf(queue, newCapacity);
}
优先队列的UML图如下:
优先队列的工作原理
优先队列:堆(建堆、排序,堆是一种特殊的树),为了读写平衡,PriorityQueue默认最小堆,可以通过Comparator接口干预成最大堆。该堆中有两个主要的方法:升序操作 siftUp 、降序操作 siftDown,建堆时用siftDown,添加元素时用到了siftUp。
自行理解下移除函数吧(它同时用到了siftUp与siftDown方法)。。。
private E removeAt(int i) {
modCount++;
int s = --size;
if (s == i) // removed last element
queue[i] = null;
else {
E moved = (E) queue[s];
queue[s] = null;
siftDown(i, moved);
if (queue[i] == moved) {
siftUp(i, moved);
if (queue[i] != moved)
return moved;
}
}
return null;
}
从队列中删除第i个元素。通常情况下,这种方法会将元素保留i-1以内不受影响。如果恰好是最好一个元素,那么移除它返回null。为了保持堆不变,它必须将列表中较后的元素替换为i元素较前的元素,这种情况下,此方法返回以前位于列表末尾的元素、现在位于i之前的某个位置。
3. 总结
ArrayDeque采用双端队列对数组进行了充分利用,PriorityQueue堆基于权值的排序性能进行了优化。