在业务场景中,处理一个任务队列,可能须要依照某种优先级顺序,这时,Java中的PriorityQueue(优先队列)即可以派上用场。优先队列的原理与堆排序密不可分,能够参考我以前的一篇博客:html
堆排序总结与实现数组
PriorityQueue中维护一个Queue[]数组,在逻辑上把它理解成一个小根堆或大根堆,即一个彻底二叉树,每个三元组中父节点小于两个孩子结点(小根堆,若是是大于则是大根堆)。本博客以小根堆来进行说明,由于PriorityQueue默认实现小根堆,即小的数先出队,固然也能够自定义Comparator实现大根堆。oop
poll()方法以下:code
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; }
能够看到,队首元素 queue[0] 出队,队尾的元素 queue[s] 进入 siftDown(0, x) 方法进行堆调整。siftDown方法以下:htm
private void siftDown(int k, E x) { if (comparator != null) siftDownUsingComparator(k, x); else siftDownComparable(k, x); } //k为开始遍历的位置,x为须要插入的值 @SuppressWarnings("unchecked") 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;//找到了key应该放入的位置 queue[k] = c; k = child; } queue[k] = key; } @SuppressWarnings("unchecked") private void siftDownUsingComparator(int k, E x) { int half = size >>> 1; while (k < half) { int child = (k << 1) + 1; Object c = queue[child]; int right = child + 1; if (right < size && comparator.compare((E) c, (E) queue[right]) > 0) c = queue[child = right]; if (comparator.compare(x, (E) c) <= 0) break; queue[k] = c; k = child; } queue[k] = x; }
能够看到,这与堆排序中的堆调整一模一样。blog
offer方法以下所示:排序
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; if (i == 0) queue[0] = e; else siftUp(i, e); return true; }
一样,其核心在于 siftUp(i, e) 方法。以下所示:队列
private void siftUp(int k, E x) { if (comparator != null) siftUpUsingComparator(k, x); else siftUpComparable(k, x); } @SuppressWarnings("unchecked") private void siftUpComparable(int k, E x) { Comparable<? super E> key = (Comparable<? super E>) x; while (k > 0) { int parent = (k - 1) >>> 1;//结点父节点的下标 Object e = queue[parent]; if (key.compareTo((E) e) >= 0) break;//若是结点值大于父节点,则能够放置在该三元组下 queue[k] = e;//向子节点赋值父节点的值,不用担忧某些值被覆盖,由于初始k等于size k = parent; } queue[k] = key;//最后在待插入位置赋key的值 } @SuppressWarnings("unchecked") private void siftUpUsingComparator(int k, E x) { while (k > 0) { int parent = (k - 1) >>> 1; Object e = queue[parent]; if (comparator.compare(x, (E) e) >= 0) break; queue[k] = e; k = parent; } queue[k] = x; }
此方法,是一个不断从父节点往子节点赋值的过程,直到找到适合放置插入结点值的位置。element
removeAt 方法以下所示:rem
private E removeAt(int i) { // assert i >= 0 && i < size; 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 为根节点的彻底二叉树的出队,因而执行 siftDown 方法调整最后一个元素 moved 的位置,即将该堆调整为小根堆。调整完以后,若是 moved 没有来到 i 的位置,说明 i 以上的堆结构必定符合规则;若是 moved 被调整到 i 位置,i上面的父节点有可能比 moved大,因此须要 siftUp(i, moved) 方法从 i 位置向上调整,调整为小根堆,完毕。
其实无论是 siftUp 方法仍是 siftDown 方法,都是利用了彻底二叉树的性质,经过父节点与孩子结点之间的快速访问来实现的。