文章目录
概述
一般我们认为队列都是先进先出的,在Java这里是非常灵活的。文档中有一句话是非常经典的:
Queues typically, but do not necessarily, order elements in a FIFO (first-in-first-out) manner. Among the exceptions are priority queues, which order elements according to a supplied comparator, or the elements’ natural ordering, and LIFO queues (or stacks) which order the elements LIFO (last-in-first-out). Whatever the ordering used, the head of the queue is that element which would be removed by a call to {@link #remove()} or {@link #poll()}. In a FIFO queue, all new elements are inserted at the tail of the queue. Other kinds of queues may use different placement rules. Every {@code Queue} implementation must specify its ordering properties.
意思是:
队列通常(但不一定)以FIFO(先进先出)方式对元素排序。例外情况包括优先队列(根据提供的比较器对元素排序)和LIFO队列(或堆栈)(后进先出)。无论使用什么顺序,队列的头部都是通过调用{@link #remove()}或{@link #poll()}来删除的元素。在FIFO队列中,所有新元素都插入到队列的尾部。其他类型的队列可能使用不同的放置规则。每个{@code Queue}实现都必须指定其有序属性。
Queue只是一个接口,具体的实现要靠子类来完成,如果想要使用优先队列可以选择 PriorityQueue ,如果仅仅就想用到队列的普通功能一般会选择LinkedList
我们看一下Queue接口的方法
(这里忽略插入类型不满足,插入的元素是空指针等的情况)
public interface Queue<E> extends Collection<E> {
boolean add(E e);
//如果可以在不违反容量限制的情况下立即将指定的元素插入此队列,成功后返回{@code true},如果当前没有空间可用,则抛出{@code IllegalStateException}。
boolean offer(E e);
//如果可以在不违反容量限制的情况下立即将指定的元素插入到此队列中。当使用受容量限制的队列时,此方法通常比{add}更可取,因为{add}会因为抛出异常而导致插入失败。
//对比
//add()和offer()都是向队列中添加一个元素。一些队列有大小限制,因此如果想在一个满的队列中加入一个新项,调用 add()方法就会抛出一个 unchecked 异常,而调用 offer()方法会返回 false。因此就可以在程序中进行有效的判断!
E remove();
E poll();
//类似于add()和offer()的关系
//remove()和 poll() 方法都是从队列中删除第一个元素。如果队列元素为空,调用remove() 的行为与 Collection 接口的版本相似会抛出异常,但是新的 poll() 方法在用空集合调用时只是返回 null。因此新的方法更适合容易出现异常条件的情况。
E element();
E peek();
//类似于add()和offer()的关系
//element()和 peek() 用于在队列的头部检索元素(不删除),返回值是头部元素。在队列为空时, element()抛出一个异常,而 peek()返回 null。
}
上面很详细地说明了Queue接口定义的方法的要求的功能
下面看看PriorityQueue 它继承自Queue,为优先队列。优先队列是基于堆的。
链表,二叉查找树,都可以提供插入和删除最大值或者最小值这两种操作,但是为什么不用它们却偏偏使用堆。原因在于应用前两者需要较高的时间复杂度。
对于链表的实现,插入需要O(1),删除最小或最大需要遍历链表,故需要O(N)。
当然这个看实现方式 ,插入与删除最大或最小 难以兼得
对于二叉查找树,这两种操作都需要O(logN);而且随着不停的删除最小或最大的操作,二叉查找树会变得非常不平衡;同时使用二叉查找树有些浪费,因此很多操作根本不需要。
对于堆这两种操作的最坏时间复杂度为O(N),而插入的平均时间复杂度为常数时间,即O(1)。
下面让我们看一下源码:
字段部分:
@SuppressWarnings("unchecked")
public class PriorityQueue<E> extends AbstractQueue<E>
implements java.io.Serializable {
private static final long serialVersionUID = -7720805057305804111L; //序列化版本ID
private static final int DEFAULT_INITIAL_CAPACITY = 11;
transient Object[] queue; // 用来存放元素
int size; //优先队列的大小
private final Comparator<? super E> comparator; //比较器,用于元素排序
transient int modCount; // 与迭代器相关
对transient的解释:
Java的serialization提供了一种持久化对象实例的机制。当持久化对象时,可能有一个特殊的对象数据成员,我们不想用serialization机制来保存它。为了在一个特定对象的一个域上关闭serialization,可以在这个域前加上关键字transient。当一个对象被序列化的时候,transient型变量的值不包括在序列化的表示中,然而非transient型的变量是被包括进去的。
它的构造方法 主要涉及到优先队列的创建(含大小的分配,默认为11)
这里展示了构造方法初始化优先队列的情况 以及相关的方法
关于c instanceof SortedSet<?> 是判断c是否是 SortedSet<?>的实例
以及c.getClass() == PriorityQueue.class 意思是c对应的Class类是否是PriorityQueue
一个类无论如何有多少个实例,这些实例都只对应一个Class类 也可以通过这个方法来判断一个实例是不是另一个类的实例
//构造方法 创建一个初始容量为11的优先队列
public PriorityQueue() {
this(DEFAULT_INITIAL_CAPACITY, null); //第二个参数是比较器
}
//构造方法 创建一个初始容量为指定值的优先队列
public PriorityQueue(int initialCapacity) { //第二个参数是比较器
this(initialCapacity, null);
}
// 使用默认初始容量和创建PriorityQueue 其元素按照指定的比较器排序。
public PriorityQueue(Comparator<? super E> comparator) {
this(DEFAULT_INITIAL_CAPACITY, comparator);
}
//使用指定的初始容量创建PriorityQueue,该初始容量根据指定的比较器对其元素进行排序。
public PriorityQueue(int initialCapacity,
Comparator<? super E> comparator) {
// 实际上并不需要这样的限制,但是对于JDK1.5兼容性来说,这种限制仍然存在,应该是当时Java编译器上的兼容性问题
if (initialCapacity < 1)
throw new IllegalArgumentException();
this.queue = new Object[initialCapacity];
this.comparator = comparator;
}
//创建一个{PriorityQueue}包含指定集合中的元素。如果指定的集合是一个{SortedSet}的实例,或者是另一个{PriorityQueue},这个优先队列将按照相同的顺序排序。否则,该优先队列将根据其元素的{ Comparable natural ordering}进行排序。
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); //具体的实现方法在后面
}
}
//创建一个{PriorityQueue},其中包含指定优先队列中的元素。这个优先队列将按照与给定优先队列相同的顺序排序。
public PriorityQueue(PriorityQueue<? extends E> c) {
this.comparator = (Comparator<? super E>) c.comparator(); //设置比较器
initFromPriorityQueue(c); //具体的实现方法在后面
}
//创建一个{PriorityQueue},其中包含指定排序集中的元素。这个优先队列将按照与给定排序集中相同的顺序排序。
public PriorityQueue(SortedSet<? extends E> c) {
this.comparator = (Comparator<? super E>) c.comparator(); //设置比较器
initElementsFromCollection(c); //具体的实现方法在后面
}
/** 确保 queue[0] 存在 **/
private static Object[] ensureNonEmpty(Object[] es) {
return (es.length > 0) ? es : new Object[1];
}
private void initFromPriorityQueue(PriorityQueue<? extends E> c) {
if (c.getClass() == PriorityQueue.class) {
this.queue = ensureNonEmpty(c.toArray()); //复制元素
this.size = c.size(); //复制大小
} else {
initFromCollection(c);
}
}
private void initElementsFromCollection(Collection<? extends E> c) {
Object[] es = c.toArray(); //复制元素 为了后面的处理
int len = es.length; //复制大小 为了后面的处理
// If c.toArray incorrectly doesn't return Object[], copy it.
//如上句 就是排错的
if (es.getClass() != Object[].class)
es = Arrays.copyOf(es, len, Object[].class);
//长度为1||构造器不为空 的情况下遍历元素 确保元素没有空值
if (len == 1 || this.comparator != null)
for (Object e : es)
if (e == null)
throw new NullPointerException();
this.queue = ensureNonEmpty(es); //复制元素
this.size = len; //复制大小
}
//使用给定集合中的元素初始化队列数组。
private void initFromCollection(Collection<? extends E> c) {
initElementsFromCollection(c); //初始化
heapify(); //重新建堆
}
//返回包含此队列中所有元素的数组。元素没有特定的顺序。
public Object[] toArray() {
return Arrays.copyOf(queue, size);
}
//返回包含此队列中所有元素的数组;
//返回数组的类型是运行时的类型。 这个跟数组协变有关系
//
public <T> T[] toArray(T[] a) {
final int size = this.size;
if (a.length < size)
// Make a new array of a's runtime type, but my contents:
return (T[]) Arrays.copyOf(queue, size, a.getClass());
System.arraycopy(queue, 0, a, 0, size);
if (a.length > size)
a[size] = null;
return a;
}
上面的这段源码结尾部分有heapify()这个方法,这个会在后面去说
下面关注优先队列的方法了 开头部分出现一个字段
private static final int MAX_ARRAY_SIZE = Integer.MAX_VALUE - 8;
这是限制要分配的数组的最大大小。一些虚拟机在数组中保留一些头信息。试图分配更大的数组可能会导致OutOfMemoryError:
请求的数组大小超过V虚拟机的限制。 除了主流的Oracle 的虚拟机,Java的虚拟机还有别的种类。 这个字段自然与下面的扩容相关的方法密切相关
//增加数组的容量。传入的值是minCapacity 这个参数对后面的理解很关键
private void grow(int minCapacity) {
int oldCapacity = queue.length; //旧容量大小
// Double size if small; else grow by 50%
//如果旧容量大小小于64 就加2
//如果旧容量大小>=64 就乘以2 >>是右移一位呀
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);
}
//判断传入的值 是小于0 还是大于最大限制值的问题
private static int hugeCapacity(int minCapacity) {
if (minCapacity < 0) // overflow
throw new OutOfMemoryError();
return (minCapacity > MAX_ARRAY_SIZE) ?
Integer.MAX_VALUE :
MAX_ARRAY_SIZE;
}
优先队列核心方法
//*********************************************
//将指定的元素插入此优先队列。
public boolean add(E e) {
return offer(e); //这里用到了offer方法
}
//将指定的元素插入此优先队列。
public boolean offer(E e) {
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;
}
//对于优先队列而言 offer和add方法是相同的
//*********************************************
//获取优先队列开头的元素
public E peek() {
return (E) queue[0];
}
//这个是配合下面remove方法的 遍历寻找数组中第一个满足条件的元素 有的话返回索引 没有的话返回-1
private int indexOf(Object o) {
if (o != null) {
final Object[] es = queue;
for (int i = 0, n = size; i < n; i++)
if (o.equals(es[i]))
return i;
}
return -1;
}
public boolean remove(Object o) {
int i = indexOf(o); //获取索引
if (i == -1)
return false;
else {
removeAt(i); //移除索引为i的元素 具体实现看下面
return true;
}
}
//如果此队列包含指定的元素,则返回{true}。更正式地说,如果且仅当此队列包含至少一个元素{ e},使得{ o.equals(e)},则返回{true}。
public boolean contains(Object o) {
return indexOf(o) >= 0;
}
//返回大小
public int size() {
return size;
}
//从该优先级队列中删除所有元素。此调用返回后,队列将为空。
public void clear() {
modCount++;
final Object[] es = queue;
for (int i = 0, n = size; i < n; i++)
es[i] = null;
size = 0; //大小设置为0
}
//删除第一个元素并且重建堆
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
siftDownUsingComparator(0, x, es, n, cmp);
}
}
return result;
}
//移除特定位置的元素 并且重建堆
E removeAt(int i) {
// assert i >= 0 && i < size;
final Object[] es = queue;
modCount++;
int s = --size;
if (s == i) // removed last element
es[i] = null;
else {
E moved = (E) es[s];
es[s] = null;
siftDown(i, moved);
if (es[i] == moved) {
siftUp(i, moved);
if (es[i] != moved)
return moved;
}
}
return null;
}
//堆的上升 选择比较器 进行重建堆
private void siftUp(int k, E x) {
if (comparator != null)
siftUpUsingComparator(k, x, queue, comparator);
else
siftUpComparable(k, x, queue);
}
//堆上升的比较方法 从方法中可以看出堆是从索引为0的地方开始的 而且这是一个
private static <T> void siftUpComparable(int k, T x, Object[] es) {
Comparable<? super T> key = (Comparable<? super T>) x;
while (k > 0) {
int parent = (k - 1) >>> 1; //a >>> b a表要操作数 b表要移的位数
Object e = es[parent];
if (key.compareTo((T) e) >= 0)
break;
es[k] = e;
k = parent;
}
es[k] = key;
}
//使用特定的比较器进行比较
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;
}
//堆的下沉 选择比较器 进行重建堆
private void siftDown(int k, E x) {
if (comparator != null)
siftDownUsingComparator(k, x, queue, size, comparator);
else
siftDownComparable(k, x, queue, size);
}
private static <T> void siftDownComparable(int k, T x, Object[] es, int n) {
// assert n > 0;
Comparable<? super T> key = (Comparable<? super T>)x;
int half = n >>> 1; // loop while a non-leaf
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;
}
private static <T> void siftDownUsingComparator(
int k, T x, Object[] es, int n, Comparator<? super T> cmp) {
// assert n > 0;
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;
}
//在整个树中重建堆,不考虑调用之前元素的顺序
private void heapify() {
final Object[] es = queue;
int n = size, i = (n >>> 1) - 1;
final Comparator<? super E> cmp;
if ((cmp = comparator) == null)
for (; i >= 0; i--)
siftDownComparable(i, (E) es[i], es, n);
else
for (; i >= 0; i--)
siftDownUsingComparator(i, (E) es[i], es, n, cmp);
}
//返回用于对该队列中的元素排序的比较器,如果该队列按照其元素的{Comparable natural order}排序,则返回{ null}
public Comparator<? super E> comparator() {
return comparator;
}
剩下的内容就是和迭代器相关的了,以后会详细对迭代器有个全面的介绍 贴出源码供参考
//查阅优先队列的继承体系,它继承自collection,collection最上面继承了Itr 这个是与 Itr.remove.有管的方法
void removeEq(Object o) {
final Object[] es = queue;
for (int i = 0, n = size; i < n; i++) {
if (o == es[i]) {
removeAt(i);
break;
}
}
}
//迭代器部分 暂且不说以后会详细说一下的
public Iterator<E> iterator() {
return new Itr();
}
private final class Itr implements Iterator<E> {
private int cursor;
private int lastRet = -1;
private ArrayDeque<E> forgetMeNot;
private E lastRetElt;
private int expectedModCount = modCount;
Itr() {} // prevent access constructor creation
public boolean hasNext() {
return cursor < size ||
(forgetMeNot != null && !forgetMeNot.isEmpty());
}
public E next() {
if (expectedModCount != modCount)
throw new ConcurrentModificationException();
if (cursor < size)
return (E) queue[lastRet = cursor++];
if (forgetMeNot != null) {
lastRet = -1;
lastRetElt = forgetMeNot.poll();
if (lastRetElt != null)
return lastRetElt;
}
throw new NoSuchElementException();
}
public void remove() {
if (expectedModCount != modCount)
throw new ConcurrentModificationException();
if (lastRet != -1) {
E moved = PriorityQueue.this.removeAt(lastRet);
lastRet = -1;
if (moved == null)
cursor--;
else {
if (forgetMeNot == null)
forgetMeNot = new ArrayDeque<>();
forgetMeNot.add(moved);
}
} else if (lastRetElt != null) {
PriorityQueue.this.removeEq(lastRetElt);
lastRetElt = null;
} else {
throw new IllegalStateException();
}
expectedModCount = modCount;
}
}
//将此队列保存到流中(即序列化它)。
private void writeObject(java.io.ObjectOutputStream s)
throws java.io.IOException {
// Write out element count, and any hidden stuff
s.defaultWriteObject();
// Write out array length, for compatibility with 1.5 version
s.writeInt(Math.max(2, size + 1));
// Write out all elements in the "proper order".
final Object[] es = queue;
for (int i = 0, n = size; i < n; i++)
s.writeObject(es[i]);
}
//从流(即反序列化)中重新构造{PriorityQueue}实例。
private void readObject(java.io.ObjectInputStream s)
throws java.io.IOException, ClassNotFoundException {
// Read in size, and any hidden stuff
s.defaultReadObject();
// Read in (and discard) array length
s.readInt();
SharedSecrets.getJavaObjectInputStreamAccess().checkArray(s, Object[].class, size);
final Object[] es = queue = new Object[Math.max(size, 1)];
// Read in all elements.
for (int i = 0, n = size; i < n; i++)
es[i] = s.readObject();
// Elements are guaranteed to be in "proper order", but the
// spec has never explained what that might be.
heapify();
}
public final Spliterator<E> spliterator() {
return new PriorityQueueSpliterator(0, -1, 0);
}
final class PriorityQueueSpliterator implements Spliterator<E> {
private int index; // current index, modified on advance/split
private int fence; // -1 until first use
private int expectedModCount; // initialized when fence set
/** Creates new spliterator covering the given range. */
PriorityQueueSpliterator(int origin, int fence, int expectedModCount) {
this.index = origin;
this.fence = fence;
this.expectedModCount = expectedModCount;
}
private int getFence() { // initialize fence to size on first use
int hi;
if ((hi = fence) < 0) {
expectedModCount = modCount;
hi = fence = size;
}
return hi;
}
public PriorityQueueSpliterator trySplit() {
int hi = getFence(), lo = index, mid = (lo + hi) >>> 1;
return (lo >= mid) ? null :
new PriorityQueueSpliterator(lo, index = mid, expectedModCount);
}
public void forEachRemaining(Consumer<? super E> action) {
if (action == null)
throw new NullPointerException();
if (fence < 0) { fence = size; expectedModCount = modCount; }
final Object[] es = queue;
int i, hi; E e;
for (i = index, index = hi = fence; i < hi; i++) {
if ((e = (E) es[i]) == null)
break; // must be CME
action.accept(e);
}
if (modCount != expectedModCount)
throw new ConcurrentModificationException();
}
public boolean tryAdvance(Consumer<? super E> action) {
if (action == null)
throw new NullPointerException();
if (fence < 0) { fence = size; expectedModCount = modCount; }
int i;
if ((i = index) < fence) {
index = i + 1;
E e;
if ((e = (E) queue[i]) == null
|| modCount != expectedModCount)
throw new ConcurrentModificationException();
action.accept(e);
return true;
}
return false;
}
public long estimateSize() {
return getFence() - index;
}
public int characteristics() {
return Spliterator.SIZED | Spliterator.SUBSIZED | Spliterator.NONNULL;
}
}
/**
* @throws NullPointerException {@inheritDoc}
*/
public boolean removeIf(Predicate<? super E> filter) {
Objects.requireNonNull(filter);
return bulkRemove(filter);
}
/**
* @throws NullPointerException {@inheritDoc}
*/
public boolean removeAll(Collection<?> c) {
Objects.requireNonNull(c);
return bulkRemove(e -> c.contains(e));
}
/**
* @throws NullPointerException {@inheritDoc}
*/
public boolean retainAll(Collection<?> c) {
Objects.requireNonNull(c);
return bulkRemove(e -> !c.contains(e));
}
// A tiny bit set implementation
private static long[] nBits(int n) {
return new long[((n - 1) >> 6) + 1];
}
private static void setBit(long[] bits, int i) {
bits[i >> 6] |= 1L << i;
}
private static boolean isClear(long[] bits, int i) {
return (bits[i >> 6] & (1L << i)) == 0;
}
/** Implementation of bulk remove methods. */
private boolean bulkRemove(Predicate<? super E> filter) {
final int expectedModCount = ++modCount;
final Object[] es = queue;
final int end = size;
int i;
// Optimize for initial run of survivors
for (i = 0; i < end && !filter.test((E) es[i]); i++)
;
if (i >= end) {
if (modCount != expectedModCount)
throw new ConcurrentModificationException();
return false;
}
// Tolerate predicates that reentrantly access the collection for
// read (but writers still get CME), so traverse once to find
// elements to delete, a second pass to physically expunge.
final int beg = i;
final long[] deathRow = nBits(end - beg);
deathRow[0] = 1L; // set bit 0
for (i = beg + 1; i < end; i++)
if (filter.test((E) es[i]))
setBit(deathRow, i - beg);
if (modCount != expectedModCount)
throw new ConcurrentModificationException();
int w = beg;
for (i = beg; i < end; i++)
if (isClear(deathRow, i - beg))
es[w++] = es[i];
for (i = size = w; i < end; i++)
es[i] = null;
heapify();
return true;
}
/**
* @throws NullPointerException {@inheritDoc}
*/
public void forEach(Consumer<? super E> action) {
Objects.requireNonNull(action);
final int expectedModCount = modCount;
final Object[] es = queue;
for (int i = 0, n = size; i < n; i++)
action.accept((E) es[i]);
if (expectedModCount != modCount)
throw new ConcurrentModificationException();
}
}