JDK源码剖析之PriorityQueue优先级队列

写在前面

版本信息:
JDK1.8

PriorityQueue介绍

在数据结构中,队列分为FIFOLIFO 两种模型,分别为先进先出,后进后出先进后出,后进先出(栈) 而一切数据结构都是基于数组或者是链表实现。

在Java中,定义了Queue接口,接口中定义了CRUD的基本方法。分别add、offer、remove、poll等等,而PriorityQueue 实现此接口实现了基本的CRUD的同时拥有了自己的特性,从名字来看也能知道是优先级队列 : 保持队列头部节点是整条队列中永远是最小或者最大的节点,其实现原理就是一个小顶堆或者大顶堆。上文提及到一切数据结构都是基于数组或者是链表实现,而这里使用了数组实现。

public class PriorityQueue<E> extends AbstractQueue<E>implements java.io.Serializable {transient Object[] queue;    // 由数组实现
}public abstract class AbstractQueue<E>extends AbstractCollection<E>implements Queue<E> {}

小顶堆和大顶堆介绍

从上文描述了PriorityQueue的底层实现是小顶堆或者大顶堆,那么在看源码之前,我们需要先明白小顶堆和大顶堆如何实现~

小顶堆:一颗完全二叉树,其中任意父节点都要小于左右子节点,所以树的根节点是整棵树的最小节点

大顶堆:一颗完全二叉树,其中任意父节点都要大于左右子节点,所以树的根节点是整棵树的最大节点

Comparable和Comparator区别

在看PriorityQueue源码之前还需要分析Comparable和Comparator区别。

Comparable:类需要实现此接口,重写compareTo方法,在compareTo方法中定义比较逻辑,使用时把类强转成Comparable调用compareTo方法,把比较对象传入。所以侵入性比较强,与业务代码强耦合。

Comparator:这个就是一个比较器,只需要把A比较对象和B比较对象都传入即可,不需要于业务代码强耦合

PriorityQueue添加元素源码分析

下文直接把PriorityQueue叫成小顶堆

我们直接从offer方法入手~

public boolean offer(E e) {if (e == null)throw new NullPointerException();modCount++;        // 用于检测是否并发// 因为size从0开始,所以size的值就是数组的索引值。int i = size;// 是否需要扩容if (i >= queue.length)grow(i + 1);  // 为下次索引+1size = i + 1;// 如果是第一个,那么就直接占用数组第一个元素即可,// 因为不管是小顶堆还是大堆堆第一个都直接插入。if (i == 0)queue[0] = e;else// 非第一个节点,此时就需要调整siftUp(i, e);return true;
}

这里的逻辑比较简单,因为这里使用数组实现的小顶堆(也即使用数组实现完全二叉树),而小顶堆的第一个节点是最小的,所以当0索引直接插入即可,非0索引就需要调整小顶堆。这里应该有很多读者是第一次见数组实现二叉树,所以这里把上文的二叉树进行扩展,把数组部分画上去。

在看 siftUp调整方法前,我们看一下grow扩容方法, 因为里面有一个思路大家可以学习~

private void grow(int minCapacity) {int oldCapacity = queue.length;// 容量小于64时,扩容为 oldCapacity + oldCapacity +2 // 容量大于64,扩容为 oldCapacity + oldCapacity/2// 等同于,在容量小的时候,每次扩容大一些,当达到64这个阈值后,扩容小一些,要不然空间会太浪费了~int newCapacity = oldCapacity + ((oldCapacity < 64) ?(oldCapacity + 2) :(oldCapacity >> 1));if (newCapacity - MAX_ARRAY_SIZE > 0)newCapacity = hugeCapacity(minCapacity);// 数组拷贝迁移。queue = Arrays.copyOf(queue, newCapacity);      
}

在每次扩容的时候,会去判断,当前容量是否大于64,如果小于64就直接 原大小 * 2 + 2 扩容,如果大于64以后直接 原大小 + 原大小/2 扩容。目的是为了在容量小的时候扩容大一些,减少扩容次数。在容量达到64阈值后,扩容小一些,减少内存浪费。

下面开始讲解siftUp调整方法

private void siftUp(int k, E x) {// 用户是否传入comparator比较器if (comparator != null)siftUpUsingComparator(k, x);else// 没传入就使用Comparable// 此时类需要实现Comparable接口siftUpComparable(k, x);
}

这里讲解siftUpComparable方法,本质上两个方法没任何区别~

private void siftUpComparable(int k, E x) {Comparable<? super E> key = (Comparable<? super E>) x;while (k > 0) {// 拿到父节点// 因为是使用数组实现的一颗完全二叉树,所以直接-1 右移即可拿到当前插入节点的父节点int parent = (k - 1) >>> 1;Object e = queue[parent];// 与父节点做比较。if (key.compareTo((E) e) >= 0)// 达到用户的预期比较就直接break,要不然继续往父节点的父节点继续做比较,直到根节点break;// 没达到预期,所以把父节点插入到本次插入的节点的位置。queue[k] = e;// 拿到父节点的索引,继续往父节点的父节点做比较。k = parent;}// 插入queue[k] = key;
}

这里光看注释,肯定是看不明白的,所以以画图+注释来理解吧~

PriorityQueue获取元素源码分析

直接从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;   // help gcif (s != 0)// 调整siftDown(0, x);return result;
}

因为小顶堆或者大顶堆都是拿第一个元素,所以这里拿出第一个元素。但是每次拿完就需要调整小顶堆(调整完全二叉树),所以看到siftDown方法。

private void siftDown(int k, E x) {if (comparator != null)siftDownUsingComparator(k, x);elsesiftDownComparable(k, x);
}

本质上这两个方法没任何区别,所以继续看到siftDownComparable方法

// k为0
// x是最后一个节点。
private void siftDownComparable(int k, E x) {Comparable<? super E> key = (Comparable<? super 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 &&((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;
}

因为每次poll取走的是第一个元素,所以需要调整整个小顶堆,而第一个元素是小顶堆的根节点,所以需要调整小顶堆找到一个符合的元素作为根节点。从根节点的左右子节点开始比较,左右子节点比较出预期的节点就作为新的根节点。预期的节点作为下次比较的父节点,通过父节点再找到他的左右子节点做比较,周而复始,直到最后一个节点。

这里光看注释,肯定是看不明白的,所以以画图+注释来理解吧~

本文来自互联网用户投稿,该文观点仅代表作者本人,不代表本站立场。本站仅提供信息存储空间服务,不拥有所有权,不承担相关法律责任。如若转载,请注明出处:http://www.mzph.cn/news/71198.shtml

如若内容造成侵权/违法违规/事实不符,请联系多彩编程网进行投诉反馈email:809451989@qq.com,一经查实,立即删除!

相关文章

C#文件拷贝工具

目录 工具介绍 工具背景 4个文件介绍 CopyTheSpecifiedSuffixFiles.exe.config DataSave.txt 拷贝的存储方式 文件夹介绍 源文件夹 目标文件夹 结果 使用 *.mp4 使用 *.* 重名时坚持拷贝 可能的报错 C#代码如下 Form1.cs Form1.cs设计 APP.config Program.c…

冠达管理:紧盯必要性 追问合理性 再融资问询透露监管新动向

在“活泼资本市场&#xff0c;提振出资者决心”一系列办法落地之后&#xff0c;再融资市场整体已明确收紧&#xff0c;但审阅尺度、相关细则还有待进一步观察。有接受采访的投行人士指出&#xff0c;现在存量项目仍在持续推进&#xff0c;监管审阅要点已在问询环节有较为充沛的…

SQL注入案例

目录 一、简介 二、案例 1.发现注入点 2.寻找注入类型 3.寻找字段数 4.将传参值设为超出数据量的大值&#xff0c;联合查询找到回显位置 5.找到数据库 6.寻找库中的表 7.寻找表中列 8.查看表中数据 附&#xff1a;SQLMap注入 1.输入指令查数据库 2.输入指令查表 3…

2023全国大学生数学建模竞赛C题思路模型代码来啦

目录 一.选题建议先发布&#xff0c;思路模型代码论文第一时间更新&#xff0c;获取见文末名片 二.选题建议&#xff0c;后续思路代码论文 C 题 蔬菜类商品的自动定价与补货决策 各题分析 获取完整思路代码见此处名片 一.选题建议先发布&#xff0c;思路模型代码论文第一时…

matplotlib 使用

import matplotlib.pyplot as plt %matplotlib inlineplt.figure()#创建一个画布 plt.plot([1, 0, 9], [4, 5, 6])#点数据&#xff0c;横坐标&#xff0c;纵坐标&#xff0c;相当于&#xff08;1&#xff0c;4&#xff09;&#xff08;0&#xff0c;5&#xff09;&#xff08;9…

log4qt库的使用

log4qt库的使用 一,什么是log4qt?二,log4qt的下载三,如何集成log4qt?1.在vs2022中集成log4qt的方法:模块一:配置log4qt的步骤步骤一,将下好的log4qt库进行解压,然后再库文件中,新建build和Log4Qt文件夹步骤二,打开cmake,有两个填写路径的位置.步骤三,点击cmake的configure按钮…

C高级day2

作业 写一个1.sh脚本&#xff0c;将以下内容放到脚本中&#xff1a; 在家目录下创建目录文件&#xff0c;dir 在dir下创建dir1和dir2 把当前目录下的所有文件拷贝到dir1中&#xff0c; 把当前目录下的所有脚本文件拷贝到dir2中 把dir2打包并压缩为dir2.tar.xz 再把dir2.…

《向量数据库指南》——AI原生向量数据库Milvus Cloud 2.3稳定性

在当今的互联网时代,稳定性是所有系统和应用程序的关键要素。无论是大型数据中心还是个人电脑,稳定性都是保证正常运行和用户体验的基础。在这个背景下,我们来谈谈 Milvus,一个开源的向量数据库,它在 2.1.0 版本中引入了内存多副本的概念。 Milvus 是一个开源的向量数据库…

redhat7.6安装weblogic12c

目录 一、环境准备 二、使用root创建用户和组 三、创建部署目录 四、上传安装包 五、创建 oraInst.loc 文件 六、创建wls.rsp 响应文件 七、进行安装 八、使用 wlst.sh 离线模式创建一个域 九、启动服务 十、浏览器访问 一、环境准备 REDHAT版本&#xff1a;Redhat…

Docker从认识到实践再到底层原理(二-3)|LXC容器

前言 那么这里博主先安利一些干货满满的专栏了&#xff01; 首先是博主的高质量博客的汇总&#xff0c;这个专栏里面的博客&#xff0c;都是博主最最用心写的一部分&#xff0c;干货满满&#xff0c;希望对大家有帮助。 高质量博客汇总 然后就是博主最近最花时间的一个专栏…

OJ练习第165题——修车的最少时间

修车的最少时间 力扣链接&#xff1a;2594. 修车的最少时间 题目描述 给你一个整数数组 ranks &#xff0c;表示一些机械工的 能力值 。ranksi 是第 i 位机械工的能力值。能力值为 r 的机械工可以在 r * n2 分钟内修好 n 辆车。 同时给你一个整数 cars &#xff0c;表示总…

十五、MySQL(DCL)如何实现用户权限控制?

1、为什么要实现用户权限控制&#xff1f; 在日常工作中&#xff0c;会存在多个用户&#xff0c;为了避免某些用户对重要数据库进行“误操作”&#xff0c;从而导致严重后果&#xff0c;所以对用户进行权限控制是必须的。 2、常见的权限类型&#xff1a; ALL,ALL PRIVILEGES …

(其他) 剑指 Offer 65. 不用加减乘除做加法 ——【Leetcode每日一题】

❓ 剑指 Offer 65. 不用加减乘除做加法 难度&#xff1a;简单 写一个函数&#xff0c;求两个整数之和&#xff0c;要求在函数体内不得使用 “”、“-”、“*”、“/” 四则运算符号。 示例: 输入: a 1, b 1 输出: 2 提示&#xff1a; a, b 均可能是负数或 0结果不会溢出 …

【Linux】线程安全-信号量

文章目录 信号量原理信号量保证同步和互斥的原理探究信号量相关函数初始化信号量函数等待信号量函数释放信号量函数销毁信号量函数 信号量实现生产者消费者模型 信号量原理 信号量的原理&#xff1a;资源计数器 PCB等待队列 函数接口 资源计数器&#xff1a;对共享资源的计…

初入行的IC工程师,如何快速提高自己的竞争力?

要想成为越来越吃香的IC工程师&#xff0c;就会先经历初期的成长阶段。今天就来聊聊初入行的ICer如何快速提升自己的竞争力&#xff08;验证篇&#xff09;。 首先希望大家在选择IC行业的时候就有清晰的认知&#xff0c;这是一个不得不深耕技术的行业。我们今天所谈论的快速提…

巨人互动|游戏出海游戏出海需要考虑哪些方面?

游戏出海是指将游戏产品推向国外市场&#xff0c;以扩大用户群体和增加盈利空间&#xff0c;那么要成功地进行游戏出海&#xff0c;需要考虑哪些方面呢&#xff1f;本文小编对此来讲讲吧&#xff01; 1、目标市场选择 选择适合游戏产品的目标市场是出海的首要考虑因素&#xf…

大厂面试题之影响项目测试进度的因素有哪些?如何解决?

测试进度&#xff0c;是领导层非常关心的问题&#xff0c;测试同学把控好项目测试进度&#xff0c;必然能让面试官为你加分。 在日常测试过程中&#xff0c;要把控好测试进度&#xff0c;了解影响测试进度的因素是必不可少的&#xff0c;那么&#xff0c;影响项目测试进度的因…

监听Helm release资源

监听Helm release资源 基于helm做部署管理工具时&#xff0c;可能想要管理用户已有环境&#xff0c;这时需要将已有环境中的release信息上报到业务系统中。当用户在环境中部署新的release时&#xff0c;也需要实时监听并上报回来。下面将讲解如何去监听release资源 helm rele…

边写代码边学习之TF Attention

1. Attention 背景介绍 通常注意力机制可以使得网络资源集中到某几个需要关注的部分上&#xff0c;和有选择性的弱化对网络结果不重要的部分。网络的注意力机制来源于人的视觉注意力&#xff0c;因为人的精力有限&#xff0c;不能注意到所有的细节&#xff0c;而是有选择性的弱…

前端Vue自定义得分构成水平柱形图组件 可用于系统专业门类得分评估分析

引入Vue自定义得分构成水平柱形图组件&#xff1a;cc-horBarChart 随着技术的发展&#xff0c;传统的开发方式使得系统的复杂度越来越高&#xff0c;一个小小的改动或小功能的增加可能会导致整体逻辑的修改&#xff0c;造成牵一发而动全身的情况。为了解决这个问题&#xff0c…