1、PriorityQueue-底层数据结构

  1. 1PriorityQueue底层使用了数组来存储数据,因为优先级队列:一般使用堆数据结构实现,堆一般使用数组存储。
  2. 2、成员属性有:
  3. // 默认容量
  4. private static final int DEFAULT_INITIAL_CAPACITY = 11;
  5. // 队列使用数组保存数据
  6. transient Object[] queue;
  7. // 元素个数,即队列的大小
  8. private int size = 0;
  9. // 比较器,保证优先级,为空表示不指定,则是自然顺序(用元素自己默认的比较器)
  10. private final Comparator<? super E> comparator;
  11. // 操作次数
  12. transient int modCount = 0;
  13. private static final int MAX_ARRAY_SIZE = Integer.MAX_VALUE - 8;//容量限制

2、PriorityQueue-构造函数

  1. private static final int DEFAULT_INITIAL_CAPACITY = 11;
  2. //默认容量11
  3. public PriorityQueue() {
  4. this(DEFAULT_INITIAL_CAPACITY, null);
  5. }
  6. //指定容量
  7. public PriorityQueue(int initialCapacity) {
  8. this(initialCapacity, null);
  9. }
  10. //默认容量,指定比较器
  11. public PriorityQueue(Comparator<? super E> comparator) {
  12. this(DEFAULT_INITIAL_CAPACITY, comparator);
  13. }
  14. //指定容量,指定比较器,判断传入的容量是否合法,初始化存储元素的数组及比较器
  15. public PriorityQueue(int initialCapacity, Comparator<? super E> comparator) {
  16. if (initialCapacity < 1)
  17. throw new IllegalArgumentException();
  18. this.queue = new Object[initialCapacity];
  19. this.comparator = comparator;
  20. }
  21. //根据传入的集合构造队列
  22. public PriorityQueue(Collection<? extends E> c) {
  23. if (c instanceof SortedSet<?>) {//SortedSet类型的集合
  24. SortedSet<? extends E> ss = (SortedSet<? extends E>) c;
  25. this.comparator = (Comparator<? super E>) ss.comparator();//取c的比较器
  26. initElementsFromCollection(ss);//元素入队
  27. }
  28. else if (c instanceof PriorityQueue<?>) {//同上
  29. PriorityQueue<? extends E> pq = (PriorityQueue<? extends E>) c;
  30. this.comparator = (Comparator<? super E>) pq.comparator();
  31. initFromPriorityQueue(pq);
  32. }
  33. else {
  34. this.comparator = null;//自然顺序
  35. initFromCollection(c);
  36. }
  37. }
  38. @SuppressWarnings("unchecked")
  39. public PriorityQueue(PriorityQueue<? extends E> c) {
  40. this.comparator = (Comparator<? super E>) c.comparator();
  41. initFromPriorityQueue(c);
  42. }
  43. @SuppressWarnings("unchecked")
  44. public PriorityQueue(SortedSet<? extends E> c) {
  45. this.comparator = (Comparator<? super E>) c.comparator();
  46. initElementsFromCollection(c);
  47. }
  48. private void initFromPriorityQueue(PriorityQueue<? extends E> c) {
  49. if (c.getClass() == PriorityQueue.class) {//说明是相同的类型,直接赋值
  50. this.queue = c.toArray();
  51. this.size = c.size();
  52. } else {
  53. initFromCollection(c);
  54. }
  55. }
  56. private void initElementsFromCollection(Collection<? extends E> c) {
  57. Object[] a = c.toArray();
  58. if (c.getClass() != ArrayList.class)//底层是数组,进行数组拷贝
  59. a = Arrays.copyOf(a, a.length, Object[].class);
  60. int len = a.length;
  61. if (len == 1 || this.comparator != null)
  62. for (int i = 0; i < len; i++)
  63. if (a[i] == null)
  64. throw new NullPointerException();//不支持null元素
  65. this.queue = a;
  66. this.size = a.length;
  67. }
  68. private void initFromCollection(Collection<? extends E> c) {
  69. initElementsFromCollection(c);
  70. heapify();//堆化:从有子节点的最靠后元素开始往前,每次都调用siftDown方法来调整的过程。
  71. }

3、PriorityQueue-扩容

  1. 1PriorityQueue-扩容:扩容函数grow()类似于ArrayList里的grow()函数,就是再申请一个更大的数组,并将原数组的元素复制过去。
  2. 1-1、[!!!]旧容量小于64时,容量翻倍,旧容量大于等于64,容量只增加旧容量的一半。
  3. 2、源码Demo:
  4. private void grow(int minCapacity) {
  5. // 旧容量
  6. int oldCapacity = queue.length;
  7. // 旧容量小于64时,容量翻倍,旧容量大于等于64,容量只增加旧容量的一半
  8. int newCapacity = oldCapacity + ((oldCapacity < 64) ?
  9. (oldCapacity + 2) :
  10. (oldCapacity >> 1));
  11. // 检查是否溢出
  12. if (newCapacity - MAX_ARRAY_SIZE > 0)
  13. newCapacity = hugeCapacity(minCapacity);
  14. // 创建出一个新容量大小的新数组并把旧数组元素拷贝过去
  15. queue = Arrays.copyOf(queue, newCapacity);
  16. }
  17. private static int hugeCapacity(int minCapacity) {
  18. if (minCapacity < 0) // overflow
  19. throw new OutOfMemoryError();
  20. return (minCapacity > MAX_ARRAY_SIZE) ?
  21. Integer.MAX_VALUE :
  22. MAX_ARRAY_SIZE;
  23. }

4、PriorityQueue-添加元素

  1. 1、添加元素,主要方法有: .add(E e)和.offer(E e)
  2. //向优先队列中插入元素,在插入失败时抛出异常
  3. public boolean add(E e) {
  4. return offer(e);
  5. }
  6. //向优先队列中插入元素,在插入失败时返回false
  7. public boolean offer(E e) {
  8. //不允许放入null元素
  9. if (e == null)
  10. throw new NullPointerException();
  11. modCount++;
  12. int i = size;
  13. if (i >= queue.length)
  14. grow(i + 1); //自动扩容
  15. size = i + 1; // 元素个数加1
  16. if (i == 0) //队列原来为空,这是插入的第一个元素
  17. queue[0] = e;
  18. else
  19. //新加入的元素可能会破坏小顶堆的性质,因此需要进行必要的调整。
  20. siftUp(i, e); //调整堆结构,从下而上堆化
  21. return true;
  22. }
  23. 2、调堆结构:
  24. //调整节点顺序/位置,因为这是一个最小堆,最小堆必须要严格按照子节点大于父亲节点的顺序做数组中存放。
  25. //在k位置插入项x
  26. private void siftUp(int k, E x) {
  27. if (comparator != null)
  28. siftUpUsingComparator(k, x);
  29. else
  30. siftUpComparable(k, x);
  31. }
  32. //有比较器,调用该方法进行节点位置调整
  33. private void siftUpUsingComparator(int k, E x) {
  34. while (k > 0) {
  35. int parent = (k - 1) >>> 1;
  36. Object e = queue[parent];
  37. if (comparator.compare(x, (E) e) >= 0)
  38. break;
  39. queue[k] = e;
  40. k = parent;
  41. }
  42. queue[k] = x;
  43. }
  44. //没有比较器,调用该方法进行节点位置调整
  45. private void siftUpComparable(int k, E x) {
  46. Comparable<? super E> key = (Comparable<? super E>) x;
  47. while (k > 0) {
  48. int parent = (k - 1) >>> 1; //右移一位,相当于/2,与计算公式:parentNo = (nodeNo-1)/2对应。
  49. Object e = queue[parent];
  50. //调用比较器的比较方法:比较可以是元素的自然顺序,也可以是依靠比较器的顺序。
  51. //从k指定的位置开始,将x逐层与当前点的parent进行比较并交换
  52. if (key.compareTo((E) e) >= 0)
  53. break;
  54. queue[k] = e;
  55. k = parent;
  56. }
  57. queue[k] = key;
  58. }

4-1、PriorityQueue-offer(E e)-堆化过程示意图

容器(集合)-PriorityQueue-源码分析 - 图1

5、PriorityQueue-获取元素

  1. 1、获取元素,主要方法有: .element(E e)和.peek(E e)。【获取但不删除队首元素,也就是队列中权值最小的那个元素。】
  2. 2、源码:
  3. //当方法失败时返回null
  4. public E peek() {
  5. if (size == 0)
  6. return null;
  7. return (E) queue[0]; //根据小顶堆的性质,堆顶那个元素就是全局最小的那个;由于堆用数组表示,根据下标关系,0下标处的那个元素既是堆顶元素。所以直接返回数组0下标处的那个元素即可。
  8. }
  9. //当方法失败时抛出异常
  10. public E element() {
  11. E x = peek();
  12. if (x != null)
  13. return x;
  14. else
  15. throw new NoSuchElementException();
  16. }

5-1、PriorityQueue-peek()-示意图

容器(集合)-PriorityQueue-源码分析 - 图2

6、PriorityQueue-删除元素

  1. 1、删除元素,涉及方法:.remove()和.poll()。【获取并删除队首元素】
  2. 2、源码:
  3. //获取并删除队首元素,当方法失败时抛出异常
  4. public boolean remove(Object o) {
  5. //找到第一个满足o.equals(queue[i])元素的下标
  6. int i = indexOf(o);
  7. if (i == -1)
  8. return false;
  9. else {
  10. removeAt(i);
  11. return true;
  12. }
  13. }
  14. //删除元素位置不同,按不同情况处理
  15. private E removeAt(int i) {
  16. assert i >= 0 && i < size;
  17. modCount++;
  18. int s = --size;
  19. if (s == i) //1. 删除的是最后一个元素。直接删除即可,不需要调整。
  20. queue[i] = null;
  21. else { //2. 删除的不是最后一个元素,从删除点开始以最后一个元素为参照调用一次siftDown()即可。
  22. E moved = (E) queue[s];
  23. queue[s] = null;
  24. siftDown(i, moved);
  25. if (queue[i] == moved) {
  26. siftUp(i, moved);
  27. if (queue[i] != moved)
  28. return moved;
  29. }
  30. }
  31. return null;
  32. }
  33. //获取并删除队首元素,当方法失败时返回null
  34. public E poll() {
  35. if (size == 0)
  36. return null;
  37. int s = --size;
  38. modCount++;
  39. E result = (E) queue[0];//0下标处的那个元素就是最小的那个
  40. E x = (E) queue[s];
  41. queue[s] = null;
  42. //由于删除操作会改变队列的结构,为维护小顶堆的性质,需要进行必要的调整。
  43. if (s != 0)
  44. siftDown(0, x);
  45. return result;
  46. }
  47. //调用siftDown()方法对堆进行调整,最后返回原来0下标处的那个元素(也就是最小的那个元素)
  48. private void siftDown(int k, E x) {
  49. if (comparator != null)
  50. siftDownUsingComparator(k, x);
  51. else
  52. siftDownComparable(k, x);
  53. }
  54. //从k指定的位置开始,将x逐层向下与当前点的左右孩子中较小的那个交换,直到x小于或等于左右孩子中的任何一个为止。
  55. private void siftDownComparable(int k, E x) {
  56. Comparable<? super E> key = (Comparable<? super E>)x;
  57. // 只需要比较一半就行了,因为叶子节点占了一半的元素
  58. int half = size >>> 1; // loop while a non-leaf
  59. //首先找到左右孩子中较小的那个,记录到c里,并用child记录其下标。【自上而下堆化】
  60. while (k < half) {
  61. //寻找子节点的位置,这里加1是因为元素从0号位置开始
  62. int child = (k << 1) + 1; //leftNo = parentNo*2+1
  63. //左子节点的值
  64. Object c = queue[child];
  65. //右子节点的位置
  66. int right = child + 1;
  67. if (right < size &&
  68. ((Comparable<? super E>) c).compareTo((E) queue[right]) > 0)
  69. //左右节点取其小者
  70. c = queue[child = right];
  71. if (key.compareTo((E) c) <= 0)
  72. break;
  73. // 如果比最小的子节点大,则交换位置
  74. queue[k] = c;
  75. k = child;
  76. }
  77. // 找到正确的位置,放入元素
  78. queue[k] = key;
  79. }
  80. //从k指定的位置开始,将x逐层向下与当前点的左右孩子中较小的那个交换,直到x小于或等于左右孩子中的任何一个为止。
  81. private void siftDownUsingComparator(int k, E x) {
  82. int half = size >>> 1;
  83. //首先找到左右孩子中较小的那个,记录到c里,并用child记录其下标。【自上而下堆化】
  84. while (k < half) {
  85. int child = (k << 1) + 1;//leftNo = parentNo*2+1
  86. Object c = queue[child];
  87. int right = child + 1;
  88. if (right < size &&
  89. comparator.compare((E) c, (E) queue[right]) > 0)
  90. c = queue[child = right];
  91. if (comparator.compare(x, (E) c) <= 0)
  92. break;
  93. queue[k] = c;//然后用c取代原来的值
  94. k = child;
  95. }
  96. queue[k] = x;
  97. }

6-1、PriorityQueue-删除元素-.poll()-.siftDown()图解过程

容器(集合)-PriorityQueue-源码分析 - 图3

6-2、PriorityQueue-删除元素-.remove(Object o)-两种删除情况图解

容器(集合)-PriorityQueue-源码分析 - 图4

7、PriorityQueue-建堆过程

  1. 1PriorityQueue的建堆过程和最大堆的建堆过程基本上一样的,从有子节点的最靠后元素开始往前,每次都调用siftDown方法来调整。这个过程也叫做heapify
  2. 2、源码:
  3. private void heapify() {
  4. for (int i = (size >>> 1) - 1; i >= 0; i--)
  5. //siftDown的过程是将一个节点和它的子节点进行比较调整,保证它比它所有的子节点都要小。这个调整的顺序是从当前节点向下,一直调整到叶节点。
  6. siftDown(i, (E) queue[i]);
  7. }

8、PriorityQueue-应用场景以及测试案例

  1. 1、应用场景,队列中元素,需要按照优先队列,也就是队列里面的元素遵从一定的比较规则(比较器比较)有序。
  2. 2、测试Demo
  3. public class PriorityQueueTest {
  4. public static void main(String[] args) {
  5. PriorityQueue<Integer> queue = Queues.newPriorityQueue();
  6. test2(queue);
  7. while (!queue.isEmpty()){
  8. System.out.println(queue.poll());
  9. }
  10. }
  11. private static void test1(Queue queue){
  12. queue.add(1);
  13. queue.add(8);
  14. queue.add(3);
  15. queue.add(5);
  16. queue.add(2);
  17. queue.add(448);
  18. queue.add(328);
  19. queue.add(18);
  20. queue.add(558);
  21. queue.add(38);
  22. queue.add(98);
  23. }
  24. private static void test2(Queue queue){
  25. queue.add("a");
  26. queue.add("v");
  27. queue.add("d");
  28. queue.add("s");
  29. queue.add("z");
  30. queue.add("da");
  31. queue.add("dda");
  32. queue.add("tgfa");
  33. queue.add("yha");
  34. queue.add("ihfa");
  35. queue.add("mdtad");
  36. }
  37. }
  38. 输出结果:
  39. 1
  40. 2
  41. 3
  42. 5
  43. 8
  44. 18
  45. 38
  46. 98
  47. 328
  48. 448
  49. 558
  50. a
  51. d
  52. da
  53. dda
  54. ihfa
  55. mdtad
  56. s
  57. tgfa
  58. v
  59. yha
  60. z