1. PriorityQueue简介
PriorityQueue是一种优先队列,不同于普通队列的先进先出原则,优先队列是按照元素的优先级出列,每次出列都是优先级最高的元素。优先队列的应用很多,最典型的就是线程了,例如守护线程(GC)就是优先级比较低的一个线程。
PriorityQueue底层是通过堆(完全二叉树)这种数据结构来存储数据的,每次出列的元素都是堆中最小的(最小堆),判断元素大小的依据由使用者指定,相当于指定优先级。
文章参考自:https://www.cnblogs.com/tstd/p/5125949.html
2. PriorityQueue继承关系
PriorityQueue继承自AbstractQueue,实现了java.io.Serializable接口。
AbstractQueue实现了Queue接口,并对队列的基本方法进行了实现。
实现了 java.io.Serializable 接口:可以启用其序列化功能,能通过序列化去传输。
3. PriorityQueue实现
1. 核心参数
//定义了底层数组的最大长度 private static final int MAX_ARRAY_SIZE = Integer.MAX_VALUE - 8; //比较器,通过比较器确定优先级 private final Comparator<? super E> comparator; //修改次数,不可被序列化 transient int modCount = 0; // non-private to simplify nested class access //Object数组用来存储数据,不可被序列化 transient Object[] queue; // non-private to simplify nested class access //默认初始容量 private static final int DEFAULT_INITIAL_CAPACITY = 11; //存储的元素个数 private int size = 0; private static final long serialVersionUID = -7720805057305804111L;
从上面可以看出,PriorityQueue底层是通过数组进行数据存储的,可是为什么说是通过堆来存储的呢?因为这是基于数组实现的二叉堆,对于数组中任意位置的n上元素,其左孩子在[2n+1]位置上,右孩子[2(n+1)]位置,它的父亲则在[(n-1)/2]上,而根的位置则是[0]。
leftNo = parentNo*2+1 rightNo = parentNo*2+2 parentNo = (nodeNo-1)/2
2. 构造函数
//无参构造,默认初始容量11,比较器为空,这里要求入队的元素必须实现Comparator接口 public PriorityQueue() { this(DEFAULT_INITIAL_CAPACITY, null); } //传入一个初始容量,比较器为空,这里要求入队的元素必须实现Comparator接口 public PriorityQueue(int initialCapacity) { this(initialCapacity, null); } //传入一个比较器,默认初始容量11 public PriorityQueue(Comparator<? super E> comparator) { this(DEFAULT_INITIAL_CAPACITY, comparator); } //传入初始容量和比较器 public PriorityQueue(int initialCapacity, Comparator<? super E> comparator) { // Note: This restriction of at least one is not actually needed, // but continues for 1.5 compatibility if (initialCapacity < 1) throw new IllegalArgumentException(); this.queue = new Object[initialCapacity]; this.comparator = comparator; } //传入一个集合 public PriorityQueue(Collection<? extends E> c) { // 如果集合c是包含比较器Comparator的(SortedSet/PriorityQueue),则使用集合c的比较器来初始化队列的Comparator if (c instanceof SortedSet<?>) { SortedSet<? extends E> ss = (SortedSet<? extends E>) c; this.comparator = (Comparator<? super E>) ss.comparator(); // 从集合c中初始化数据到队列 initElementsFromCollection(ss); } else if (c instanceof PriorityQueue<?>) { PriorityQueue<? extends E> pq = (PriorityQueue<? extends E>) c; this.comparator = (Comparator<? super E>) pq.comparator(); // 从优先队列c中初始化数据到队列 initFromPriorityQueue(pq); } // 如果集合c没有包含比较器,则默认比较器Comparator为空 else { this.comparator = null; // 从集合c中初始化数据到队列 initFromCollection(c); } } //传入一个优先队列 public PriorityQueue(PriorityQueue<? extends E> c) { //使用传入队列的比较器,若是比较器为空,则需要队列内的元素实现Comparable接口 this.comparator = (Comparator<? super E>) c.comparator(); //从优先队列c中初始化数据到队列 initFromPriorityQueue(c); } //传入一个可排序的set public PriorityQueue(SortedSet<? extends E> c) { //使用传入set的比较器,若是比较器为空,则需要队列内的元素实现Comparable接口 this.comparator = (Comparator<? super E>) c.comparator(); // 从集合c中初始化数据到队列 initElementsFromCollection(c); }
3. 核心方法
//传入一个优先队列,并把当前优先队列的内容替换 private void initFromPriorityQueue(PriorityQueue<? extends E> c) { //如果该优先队列是PriorityQueue,则直接把底层数组和大小属性替换 if (c.getClass() == PriorityQueue.class) { this.queue = c.toArray(); this.size = c.size(); //否则调用根据集合的统一初始化方式 } else { initFromCollection(c); } } //传入一个集合,并把当前优先队列的内容替换 private void initElementsFromCollection(Collection<? extends E> c) { //把传入的集合转化为数组a Object[] a = c.toArray(); //如果该集合内的元素不是Object类型,则拷贝一份 if (a.getClass() != Object[].class) a = Arrays.copyOf(a, a.length, Object[].class); int len = a.length; //遍历数组,若其中存在null元素则抛出空指针异常 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; } //传入一个集合,并把当前优先队列的内容替换,然后将数组调整为一个最小堆(时间复杂度o(n)) private void initFromCollection(Collection<? extends E> c) { initElementsFromCollection(c); //将数组调整为一个最小堆 heapify(); } //对底层数组进行扩容 private void grow(int minCapacity) { //取当前数组的容量 int oldCapacity = queue.length; //如果当前数组容量小于64则扩容1倍,否则扩容0.5倍,防止数据量太大时扩容太多造成空间浪费 int newCapacity = oldCapacity + ((oldCapacity < 64) ? (oldCapacity + 2) : (oldCapacity >> 1)); //当扩容后的容量大于定义的最大值时的处理 if (newCapacity - MAX_ARRAY_SIZE > 0) newCapacity = hugeCapacity(minCapacity); //通过System.arraycopy进行扩容 queue = Arrays.copyOf(queue, newCapacity); } //大容量处理 private static int hugeCapacity(int minCapacity) { //超过int最大值,抛出内存溢出异常 if (minCapacity < 0) throw new OutOfMemoryError(); //小于int最大值大于定义的最大值,返回int最大值;小于定以的最大值,返回定义的最大值 return (minCapacity > MAX_ARRAY_SIZE) ? Integer.MAX_VALUE : MAX_ARRAY_SIZE; } //向队列中添加元素 public boolean add(E e) { return offer(e); } //按照最小堆的规则添加元素 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; //若队列大小为0,则直接插入根位置 if (i == 0) queue[0] = e; //否则根据元素的大小插入合适的位置,保证最小堆的结构 else siftUp(i, e); return true; } //获取队列根元素的值 public E peek() { return (size == 0) ? null : (E) queue[0]; } //获取队列中指定元素的索引位置,通过equals比较判断,不存在返回-1 private int indexOf(Object o) { if (o != null) { for (int i = 0; i < size; i++) if (o.equals(queue[i])) return i; } return -1; } //将指定元素移除队列 public boolean remove(Object o) { int i = indexOf(o); if (i == -1) return false; else { removeAt(i); return true; } } //移除指定元素,仅同包类可访问 boolean removeEq(Object o) { for (int i = 0; i < size; i++) { if (o == queue[i]) { removeAt(i); return true; } } return false; } //判断队列中是否包含指定元素 public boolean contains(Object o) { return indexOf(o) != -1; } //通过System.arraycopy将队列转为Object数组返回 public Object[] toArray() { return Arrays.copyOf(queue, size); } //通过System.arraycopy将队列转为指定类型的数组返回 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; } //获取一个迭代器(内部类实现的) public Iterator<E> iterator() { return new Itr(); } //返回队列的大小 public int size() { return size; } //清空队列,通过for循环遍历将所有元素置为null public void clear() { modCount++; for (int i = 0; i < size; i++) queue[i] = null; size = 0; } //将队列根元素取出,并重新调整最小堆结构 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 E removeAt(int i) { // assert i >= 0 && i < size; modCount++; int s = --size; //移除最后一个元素,直接移除即可 if (s == i) queue[i] = null; //否则需要重新调整最小堆的结构 else { //取最后一个元素moved E moved = (E) queue[s]; //将最后一个元素置为空 queue[s] = null; //把moved元素从i位置开始,根据最小堆的结构向下调整 //最小堆是父节点小于等于任意一个子节点所以moved元素可能比i位置的子节点的值大 //这时候就需要把moved元素跟其中一个比较小的子节点交换位置,以此类推,直到满足最小堆的结构 siftDown(i, moved); //在这之前,只是把队列的最后一个元素置为空并赋值给moved变量,所以这时候i位置的元素还未动过 //当i位置的值与moved值相等时, if (queue[i] == moved) { siftUp(i, moved); if (queue[i] != moved) return moved; } } return null; } //向上调整最小堆结构,k插入的位置,x为插入元素 private void siftUp(int k, E x) { //如果指定了比较器,则使用指定比较器的规则调整 if (comparator != null) siftUpUsingComparator(k, x); //否则使用元素默认的比较规则 else siftUpComparable(k, x); } //默认比较规则上移 private void siftUpComparable(int k, E x) { // 比较器comparator为空,需要插入的元素实现Comparable接口,用于比较大小 Comparable<? super E> key = (Comparable<? super E>) x; //不是根节点都需要进行调整处理 while (k > 0) { //取k节点的父节点 int parent = (k - 1) >>> 1; //获取父节点元素e Object e = queue[parent]; //如果插入的元素大于等于父节点,则满足最小堆的规则,终止调整,否则需要把插入的元素上移一位 if (key.compareTo((E) e) >= 0) break; //插入的元素小于父节点,将父元素放入插入元素的位置 queue[k] = e; //将插入位置调整为父节点的位置 k = parent; } //直到调整完成后,再将要插入的元素插入最终确定的位置 queue[k] = key; } //使用指定比较器的规则上移 private void siftUpUsingComparator(int k, E x) { while (k > 0) { //取k节点的父节点 int parent = (k - 1) >>> 1; //获取父节点元素e Object e = queue[parent]; //如果插入的元素大于等于父节点,则满足最小堆的规则,终止调整,否则需要把插入的元素上移一位 if (comparator.compare(x, (E) e) >= 0) break; //插入的元素小于父节点,将父元素放入插入元素的位置 queue[k] = e; //将插入位置调整为父节点的位置 k = parent; } //直到调整完成后,再将要插入的元素插入最终确定的位置 queue[k] = x; } //向下调整最小堆结构,k插入的位置,x为插入元素 private void siftDown(int k, E x) { //如果指定了比较器,则使用指定比较器的规则调整 if (comparator != null) siftDownUsingComparator(k, x); //否则使用元素默认的比较规则 else siftDownComparable(k, x); } //使用元素默认的比较规则下移,k插入的位置,x为插入元素 private void siftDownComparable(int k, E x) { // 比较器comparator为空,需要插入的元素实现Comparable接口,用于比较大小 Comparable<? super E> key = (Comparable<? super E>)x; //这里主要是去掉所有的叶子节点(没有子节点),排除不需要下移的情况,当k<half说明k节点有子节点 int half = size >>> 1; while (k < half) { //取k节点的左子节点 int child = (k << 1) + 1; //取k节点的左子节点元素c Object c = queue[child]; //取k节点的右子节点 int right = child + 1; //k<half说明k节点有子节点,但不一定有两个,即一定有左子节点,不一定有右子节点 //right < size说明k节点是有两个节点的,当左节点大于右节点时,将右节点的值赋给c,即找出k的最小子节点 if (right < size && ((Comparable<? super E>) c).compareTo((E) queue[right]) > 0) c = queue[child = right]; //要插入的元素小于子节点的最小节点,则终止调整,否则需要下移 if (key.compareTo((E) c) <= 0) break; //k的最小子节点赋给k queue[k] = c; //将插入的置为调整为最小子节点的位置 k = child; } //调整完成后,将要插入的元素放入调整后的位置 queue[k] = key; } //使用指定比较器的比较规则下移,k插入的位置,x为插入元素 private void siftDownUsingComparator(int k, E x) { //这里主要是去掉所有的叶子节点(没有子节点),排除不需要下移的情况,当k<half说明k节点有子节点 int half = size >>> 1; while (k < half) { //取k节点的左子节点 int child = (k << 1) + 1; //取k节点的左子节点元素c Object c = queue[child]; //取k节点的右子节点 int right = child + 1; //k<half说明k节点有子节点,但不一定有两个,即一定有左子节点,不一定有右子节点 //right < size说明k节点是有两个节点的,当左节点大于右节点时,将右节点的值赋给c,即找出k的最小子节点 if (right < size && comparator.compare((E) c, (E) queue[right]) > 0) c = queue[child = right]; //要插入的元素小于子节点的最小节点,则终止调整,否则需要下移 if (comparator.compare(x, (E) c) <= 0) break; //k的最小子节点赋给k queue[k] = c; //将插入的置为调整为最小子节点的位置 k = child; } //调整完成后,将要插入的元素放入调整后的位置 queue[k] = x; } //调整整个最小堆的结构 private void heapify() { //因为调用的是下移的操作,所以需要去掉所有的叶子节点 for (int i = (size >>> 1) - 1; i >= 0; i--) siftDown(i, (E) queue[i]); } //返回指定的比较器 public Comparator<? super E> comparator() { return comparator; } //序列化队列 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". for (int i = 0; i < size; i++) s.writeObject(queue[i]); } //反序列化序列 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.getJavaOISAccess().checkArray(s, Object[].class, size); queue = new Object[size]; // Read in all elements. for (int i = 0; i < size; i++) queue[i] = s.readObject(); // Elements are guaranteed to be in "proper order", but the // spec has never explained what that might be. heapify(); }