目录
1 概述
2 源码分析
1 概述
PriorityQueue 是一个优先级队列,其底层原理采用二叉堆实现。我们先来看看它的类声明:
public class PriorityQueue<E> extends AbstractQueue<E> implements java.io.Serializable
PriorityQueue 继承了 AbstractQueue 抽象类,具有队列的基本特性。
2 源码分析
2.1 类成员变量
// 队列数据 transient Object[] queue; // 大小 private int size = 0; // 比较器 private final Comparator<? super E> comparator;
从类成员变量我们可以知道 PriorityQueue 底层采用数组存储数据,comparator 的实现决定了其实一个最大堆还是最小堆。默认情况下 PriorityQueue 是个最小堆。
2.2 构造方法
PriorityQueue 一共有 7 个构造方法。
public PriorityQueue() { this(DEFAULT_INITIAL_CAPACITY, null); } public PriorityQueue(int initialCapacity) { this(initialCapacity, null); } public PriorityQueue(Comparator<? super E> comparator) { this(DEFAULT_INITIAL_CAPACITY, comparator); } public PriorityQueue(int initialCapacity, Comparator<? super E> comparator) { if (initialCapacity < 1) throw new IllegalArgumentException(); this.queue = new Object[initialCapacity]; this.comparator = comparator; } // 传入集合初始值 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); } // 传入SortedSet初始值 public PriorityQueue(SortedSet<? extends E> c) { this.comparator = (Comparator<? super E>) c.comparator(); initElementsFromCollection(c); }
PriorityQueue 的构造方法比较多,但其功能都类似。如果传入的是普通集合,那么会将其数据复制,最后调用 heapify 方法进行二叉堆的初始化操作。但如果传入的数据是 SortedSet 或 PriorityQueue 这些已经有序的数据,那么就直接按照顺序复制数据即可。
2.3 核心方法
对于 PriorityQueue 来说,其核心方法有:获取、插入、删除、扩容。
2.3.1 获取
PriorityQueue 没有查询方法,取而代之的是获取数据的 peek 方法。
public E peek() { return (size == 0) ? null : (E) queue[0]; }
如果队列为空,那么返回 null 值,否则返回队列的第一个元素(即最大或最小值)。
2.3.2 插入
PriorityQueue 的数据插入过程,其实就是往二叉堆插入数据的过程。
public boolean add(E e) { return offer(e); } public boolean offer(E e) { if (e == null) throw new NullPointerException(); modCount++; int i = size; // 1.容量不够,进行扩容 if (i >= queue.length) grow(i + 1); size = i + 1; // 2.如果队列为空那么直接插入第一个节点 // 否则插入末尾节点后进行上浮操作 if (i == 0) queue[0] = e; else siftUp(i, e); return true; } private void siftUp(int k, E x) { if (comparator != null) siftUpUsingComparator(k, x); else // 3.采用默认的比较器 siftUpComparable(k, x); } private void siftUpComparable(int k, E x) { Comparable<? super E> key = (Comparable<? super E>) x; while (k > 0) { // 4.将插入节点与父节点比较 // 如果插入节点大于等于父节点,那么说明符合最小堆性质 // 否则交换插入节点与父节点的值,一直到堆顶 int parent = (k - 1) >>> 1; Object e = queue[parent]; if (key.compareTo((E) e) >= 0) break; queue[k] = e; k = parent; } queue[k] = key; }
插入的代码最终的逻辑是在 siftUpComparable 方法中,而该方法其实就是我们上面所说二叉堆插入逻辑的实现。
2.3.3 删除
PriorityQueue 的数据删除过程,其实就是将数据从二叉堆中删除的过程。
public boolean remove(Object o) { int i = indexOf(o); if (i == -1) return false; else { removeAt(i); return true; } } private E removeAt(int i) { // assert i >= 0 && i < size; modCount++; int s = --size; // 1.删除的是末尾节点,那么直接删除即可 if (s == i) // removed last element queue[i] = null; else { E moved = (E) queue[s]; queue[s] = null; // 2.对删除节点做下沉操作 siftDown(i, moved); if (queue[i] == moved) { // 3.queue[i] == moved 表示删除节点根本没下沉 // 意思是其就是该子树最小的节点 // 这种情况下就需要进行上浮操作 // 因为可能出现删除节点父节点大于删除节点的情况 siftUp(i, moved); if (queue[i] != moved) return moved; } } return null; } private void siftDown(int k, E x) { if (comparator != null) siftDownUsingComparator(k, x); else siftDownComparable(k, x); } private void siftDownComparable(int k, E x) { Comparable<? super E> key = (Comparable<? super E>)x; int half = size >>> 1; // loop while a non-leaf while (k < half) { int child = (k << 1) + 1; // assume left child is least Object c = queue[child]; int right = child + 1; if (right < size && ((Comparable<? super E>) c).compareTo((E) queue[right]) > 0) c = queue[child = right]; if (key.compareTo((E) c) <= 0) break; queue[k] = c; k = child; } queue[k] = key; }
2.3.4 offer
因为 PriorityQueue 是队列,所以有 offer 操作。
对于 offer 操作来说,其实就是相当于往数组未插入数据,其逻辑细节我们在插入 add 方法中已经说到。
2.3.5 poll
因为 PriorityQueue 是队列,同样会有 poll 操作。而 poll 操作其实就是弹出队列头结点,相当于删除头结点。
public E poll() { if (size == 0) return null; int s = --size; modCount++; // 弹出头结点 E result = (E) queue[0]; E x = (E) queue[s]; queue[s] = null; // 做下沉操作 if (s != 0) siftDown(0, x); return result; }
之前我们说过删除节点的逻辑,即拿末尾节点值替代删除节点,然后做下沉操作。但是这里因为删除节点是根节点了,所以不需要做上浮操作。
扩容
当往队列插入数据时,如果队列容量不够则会进行扩容操作。
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); }
PriorityQueue 的扩容非常简单。如果原来的容量小于 64,那么扩容为原来的两倍,否则扩容为原来的 1.5 倍。
3 总结
PriorityQueue 的实现是建立在二叉堆之上的,所以弄懂二叉堆就相当于弄懂了 PriorityQueue。PriorityQueue 默认情况下是最小堆,我们可以改变传入的比较器,使其成为最大堆。
0