2025年5月18日 星期日 乙巳(蛇)年 二月廿 设为首页 加入收藏
rss
您当前的位置:首页 > 计算机 > 编程开发 > Java

【JDK源码】PriorityQueue源码分析

时间:05-13来源:作者:点击数:53

PriorityQueue源码分析

1.简介

优先级队列,是0个或多个元素的集合,集合中的每个元素都有一个权重值,每次出队都弹出优先级最大或最小的元素。

一般来说,优先级队列使用堆来实现。

2.主要属性

  • // 默认容量
  • private static final int DEFAULT_INITIAL_CAPACITY = 11;
  • // 存储元素的数组
  • transient Object[] queue; // non-private to simplify nested class access
  • // 元素个数
  • private int size = 0;
  • // 比较器
  • private final Comparator<? super E> comparator;
  • // 修改次数
  • transient int modCount = 0; // non-private to simplify nested class access

从源码可以看出:

  1. 默认容量是11
  2. queue:元素存储在数组中,这跟我们之前说的堆一般使用数组来存储是一致的
  3. comparator:比较器,在优先级队列中,也有两种方式比较元素,一种是元素的自然顺序,一种是通过比较器来比较
  4. modCount:修改次数,有这个属性表示PriorityQueue也是fast-fail

3.入队

  • //入队
  • public boolean add(E e) {
  • //调用的offer(E e)
  • return offer(e);
  • }
  • //入队
  • public boolean offer(E e) {
  • //不支持null元素,否则抛出异常
  • if (e == null)
  • throw new NullPointerException();
  • //修改次数加一
  • modCount++;
  • //取size
  • int i = size;
  • //元素个数达到最大容量,扩容
  • if (i >= queue.length)
  • grow(i + 1);
  • //元素个数加一
  • size = i + 1;
  • //如果当前队列还没有元素,直接插入到数组的第一个位置
  • if (i == 0)
  • queue[0] = e;
  • else
  • //否则,插入元素到数组的size位置,也就是最后一个元素的下一位
  • //这里size不是数组大小,而是元素个数
  • //然后做自下而上的堆化
  • siftUp(i, e);
  • return true;
  • }
  • 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) {
  • //找到父节点的位置
  • //因为元素从0开始,所以减一后在除2
  • int parent = (k - 1) >>> 1;
  • //父节点的值
  • Object e = queue[parent];
  • //比较插入的元素与父节点的值
  • //如果比父节点大,则跳出循环
  • //否则交换位置
  • if (key.compareTo((E) e) >= 0)
  • break;
  • //与父节点交换位置
  • queue[k] = e;
  • //现在插入的元素位置移到了父节点的位置
  • //继续与父节点再比较
  • k = parent;
  • }
  • //最后找到应该插入的位置,放入元素
  • queue[k] = key;
  • }

整个流程:

  1. 入队不允许null元素
  2. 如果数组不够用了,先扩容
  3. 如果还没有元素,就插入下标0的位置
  4. 如果有元素了,就插入到最后一个元素往后的一个位置(实际并没有插入哈)
  5. 自下而上堆化,一直往上跟父节点比较
  6. 如果比父节点小,就与父节点交换位置,直到出现比父节点大为止
  7. 由此可见,PriorityQueue是一个小顶堆

4.扩容

  • private void grow(int minCapacity) {
  • //旧容量
  • int oldCapacity = queue.length;
  • // 旧容量小于64时,容量翻倍
  • // 旧容量大于等于64,容量只增加旧容量的一半
  • int newCapacity = oldCapacity + ((oldCapacity < 64) ?
  • (oldCapacity + 2) :
  • (oldCapacity >> 1));
  • // 检查是否溢出
  • if (newCapacity - MAX_ARRAY_SIZE > 0)
  • newCapacity = hugeCapacity(minCapacity);
  • // 创建出一个新容量大小的新数组并把旧数组元素拷贝过去
  • queue = Arrays.copyOf(queue, newCapacity);
  • }
  • private static int hugeCapacity(int minCapacity) {
  • if (minCapacity < 0) // overflow
  • throw new OutOfMemoryError();
  • return (minCapacity > MAX_ARRAY_SIZE) ?
  • Integer.MAX_VALUE :
  • MAX_ARRAY_SIZE;
  • }
  1. 当数组比较小**(小于64)的时候每次扩容容量翻倍**
  2. 当数组比较大**(大于等于64)的时候每次扩容只增加一半的容量**

5.出队

  • @SuppressWarnings("unchecked")
  • public E poll() {
  • // 如果size为0,说明没有元素
  • if (size == 0)
  • return null;
  • // 弹出元素,元素个数减1
  • 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 siftDown(int k, E x) {
  • // 根据是否有比较器,选择不同的方法
  • if (comparator != null)
  • siftDownUsingComparator(k, x);
  • else
  • siftDownComparable(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) {
  • // 寻找子节点的位置,这里加1是因为元素从0号位置开始
  • 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;
  • }

整个流程:

  1. 将队列首元素弹出
  2. 将队列末元素移到队列首
  3. 自上而下堆化,一直往下与最小的子节点比较
  4. 如果比最小的子节点大,就交换位置,再继续与最小的子节点比较
  5. 如果比最小的子节点小,就不用交换位置了,堆化结束

6.取队首元素

  • public E peek() {
  • //队列为空返回null
  • //否则返回堆顶元素也就是queue[0]
  • return (size == 0) ? null : (E) queue[0];
  • }
  1. 如果有元素就取下标0的元素
  2. 如果没有元素就返回null

7.总结

  1. PriorityQueue是一个小根堆
  2. PriorityQueue是非线程安全的
  3. PriorityQueue不是有序的只有堆顶存储着最小的元素
  4. 入队就是堆的插入元素的实现
  5. 出队就是堆的删除元素的实现
方便获取更多学习、工作、生活信息请关注本站微信公众号城东书院 微信服务号城东书院 微信订阅号
推荐内容
相关内容
栏目更新
栏目热门