HashMap源码详解

提醒你一下,用电脑或者平板看,手机屏幕小,图片会看不清楚,源码不方便看

基础前置

看该篇文章之前先看看这篇基础文章       HashMap底层原理-CSDN博客 

 先来看HashMap里面的一些参数。

1.DEFAULT_INITIAL_CAPACITY 默认的数组初始容量

2. DEFAULT_LOAD_FACTOR 默认的加载因子。(hashmap数组扩容机制:  扩容阈值 = 数组容量 * 加载因子)

3. table 你知道的,hashmap底层是数组+链表+红黑树,不知道就先看上面说的基础文章,table就是这里面的数组。

4. size 集合中元素个数。

 static final int DEFAULT_INITIAL_CAPACITY = 1 << 4; // aka 16static final float DEFAULT_LOAD_FACTOR = 0.75f;transient Node<K,V>[] table;transient int size;

 这个是上面第三行代码里面的Node简化后的代码,源码还重写了equals和hashcode方法等,先不关注这些,看看简化后的就行。

64fc9b7940d64e3fa7e07632aaa9a513.png

构造方法

    idea中ctrl+鼠标左键点进去看看源码。

 404fbdcedee4448ea9d424edfe47ea19.png

查看下面源码可知:

1. HashMap是懒加载,也就是说,Hashmap在初始化的时候没有创建数组,只是初始化了加载因子

2. 无参构造函数里面,加载因子初始化为0.75

  public HashMap() {this.loadFactor = DEFAULT_LOAD_FACTOR; // all other fields defaulted}

put方法源码

 直接看代码可能不够直观,我们结合流程图来看,仔细耐心看完,顺着把每个流程走一下,待会儿看源码就会清楚很多。 实在看不懂的话,看源码的时候再来结合图片看。

        下图threshold就是上面提到的扩容阈值,扩容阈值 = 数组容量 * 加载因子。

        而且你可能会迷惑为什么右下角转化为红黑树只需要判断链表长度,不应该还需要判断数组长度吗? 这个你往下看,我会解答的。

       并且,转化条件是链表长度>=8且数组长度>=64,下图是我网上找的,有点小错误,这里修正一下。133221d11c4c41f4a7ca7e49dc39b0af.png

HashMap里面的put方法

 public V put(K key, V value) {return putVal(hash(key), key, value, false, true);}

第一个参数hash(key),其实就是计算key的哈希值,对应数组哪个下标位置。

   static final int hash(Object key) {int h;return (key == null) ? 0 : (h = key.hashCode()) ^ (h >>> 16);}

现在看看上述的putVal方法。注释写了很久,各位爷给个三连,不懂的留言评论区,24小时内回复。

上述图片代码贴在下面了

final V putVal(int hash, K key, V value, boolean onlyIfAbsent,boolean evict) {Node<K,V>[] tab; Node<K,V> p; int n, i;//判断数组是否初始化,我们上面介绍过hashmap是懒加载,创建hashmap不会初始化,第一次插入值才初始化。    if ((tab = table) == null || (n = tab.length) == 0)//如果没有初始化,调用resize()进行初始化,第一次初始化长度为16。resize方法源码后面也会带着大家看,现在先不着急。n = (tab = resize()).length;//(n - 1) & hash是与运算,算出了当前key、value插入到数组对应位置下标。if ((p = tab[i = (n - 1) & hash]) == null)//如果该下标为null,那么直接插入就行,进而转到58行代码tab[i] = newNode(hash, key, value, null);//如果不为null,就要进一步判断else {Node<K,V> e; K k;//判断该位置key和新传来的key是否一样if (p.hash == hash &&((k = p.key) == key || (key != null && key.equals(k))))//如果一样,证明为修改操作,把该值赋值给p,直接转到48行代码e = p;//判断是不是红黑树else if (p instanceof TreeNode)//如果是红黑树,走红黑树的添加逻辑e = ((TreeNode<K,V>)p).putTreeVal(this, tab, hash, key, value);//既不是红黑树,key也不相同,证明是链表。else {//遍历链表for (int binCount = 0; ; ++binCount) {//找到next节点,如果为null,证明遍历到尾部了if ((e = p.next) == null) {//把新值放入链表尾部p.next = newNode(hash, key, value, null);//链表新插入节点,有转换成红黑树的可能,要判断链表长度是否大于等于8if (binCount >= TREEIFY_THRESHOLD - 1) // -1 for 1st//如果是,就转化成红黑树,这个转化操作就不带大家看了,感兴趣自己研究一下。treeifyBin(tab, hash);break;}//判断是否有key相同的值,如果有那就是修改操作,break后转到48行代码继续执行if (e.hash == hash &&((k = e.key) == key || (key != null && key.equals(k))))break;p = e;}}//e是修改操作中存放原数据的变量if (e != null) { // existing mapping for key//新值覆盖旧值V oldValue = e.value;if (!onlyIfAbsent || oldValue == null)e.value = value;afterNodeAccess(e);return oldValue;}}//计数器,计算当前节点修改次数++modCount;//上面插入成功后执行++size,然后判断数据量是否大于阈值,大于的话数组就进行resize()扩容   if (++size > threshold)resize();afterNodeInsertion(evict);return null;
}

        看到这,你应该大致明白了底层逻辑,但是有个坑我故意留给大家了,35行代码为什么只需要判断链表长度大于8就转成红黑树呢?  不是说,要链表长度>=8且数组长度>=64吗。如果你对hashmap代码真的非常感兴趣,应该去看了第37行代码,方法内部的源码,treeifyBin(tab, hash);

        现在我来带大家简单看看

        下面的MIN_TREEIFY_CAPACITY=64,当数组长度小于64的时候执行扩容操作,否则才执行链表转化红黑树操作。至此,put的基本流程已经结束。

final void treeifyBin(Node<K,V>[] tab, int hash) {int n, index; Node<K,V> e;if (tab == null || (n = tab.length) < MIN_TREEIFY_CAPACITY)resize();else if ((e = tab[index = (n - 1) & hash]) != null) {TreeNode<K,V> hd = null, tl = null;do {TreeNode<K,V> p = replacementTreeNode(e, null);if (tl == null)hd = p;else {p.prev = tl;tl.next = p;}tl = p;} while ((e = e.next) != null);if ((tab[index] = hd) != null)hd.treeify(tab);}}

  

现在接着讲讲扩容机制吧

扩容机制

        这个就看看流程图吧,我详细给大家讲讲流程图。源码我后续补上,脑袋转不动了。

        橙色部分是第一次插入数据扩容,数组还没有初始化的时候。

        蓝色部分是第二次及以后的扩容了,直接扩2倍新建数组,然后遍历旧数组把值加入到新数组,这里又分为三种情况。

1.  只有一个节点,取出当前节点哈希值,与新数组长度进行与运算找到在新数组存储的下标位置进行存储

2.  如果是红黑树,直接走红黑树添加逻辑。

3.  如果是链表,遍历链表,进行当前值的hash和旧容量的与运算,也就是e.hash & oldCap,如果该值等于0,该值在新数组的下标和就数组一样,如果该值不等于0,那么放入新数组下标为j+oldCap(旧数组下标+旧容量)的地方,这样看来,第三步链表的转移操作是有可能把链表拆分的。d6ccb25263614cb6b5c9f1b468685d18.png

看完后你可以来看看这位大佬的hashmap提问,看你能答上来几道,里面有答案。

HashMap夺命14问,你能坚持到第几问?-腾讯云开发者社区-腾讯云

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

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

相关文章

09 —— Webpack搭建开发环境

搭建开发环境 —— 使用webpack-dev-server 启动Web服务&#xff0c;自动检测代码变化&#xff0c;有变化后会自动重新打包&#xff0c;热更新到网页&#xff08;代码变化后&#xff0c;直接替换变化的代码&#xff0c;自动更新网页&#xff0c;不用手动刷新网页&#xff09; …

动态反馈控制器(DFC)和 服务率控制器(SRC);服务率和到达率简单理解

目录 服务率和到达率简单理解 服务率 到达率 排队论中的应用 论文解析:队列等待成本动态感知控制模型 动态反馈和队列等待成本意识: 服务速率调整算法: 动态反馈控制器(DFC)和 服务率控制器(SRC) SRC公式4的原理 算力资源分配系统中的调整消耗 举例说明 服务…

九、FOC原理详解

1、FOC简介 FOC&#xff08;field-oriented control&#xff09;为磁场定向控制&#xff0c;又称为矢量控制&#xff08;vectorcontrol&#xff09;&#xff0c;是目前无刷直流电机&#xff08;BLDC&#xff09;和永磁同步电机&#xff08;PMSM&#xff09;高效控制的最佳选择…

web-03

CSS回顾 选择器 标签选择器 标签{}ID选择器 标签中定义ID属性。 #ID值{}类选择器 标签中使用class属性 .类名{}关于DIV/span div任意的大小的长方形&#xff0c;大小css&#xff1a; width, height控制。—换行 span-- 一行内 CSS常用属性 width/height 宽度/高度 定义&…

Excel求和如何过滤错误值

一、问题的提出 平时&#xff0c;我们在使用Excel时&#xff0c;最常用的功能就是求和了&#xff0c;一说到求和你可能想到用sum函数&#xff0c;但是如果sum的求和区域有#value #Div等错误值怎么办&#xff1f;如下图&#xff0c;记算C列中工资的总和。 直接用肯定会报错&…

蓝桥杯每日真题 - 第22天

题目&#xff1a;&#xff08;卡片&#xff09; 题目描述&#xff08;12届 C&C B组B题&#xff09; 解题思路&#xff1a; 该问题要求用数字卡片从 1 开始拼出整数&#xff0c;直到某一时刻不能拼出时停止。要确定拼到哪个最大整数&#xff0c;需要统计 每个数字“1”被用…

Ubuntu20.04 Rk3588 交叉编译ffmpeg7.0

firefly 公司出的rk3588的设备&#xff0c;其中已经安装了gcc 交叉编译工具&#xff0c;系统版本是Ubuntu20.04。 使用Ubuntu20.04 交叉编译ffmpeg_ubuntu下配置ffmpeg交叉编译器为arm-linux-gnueabihf-gcc-CSDN博客文章浏览阅读541次。ubuntu20.04 交叉编译ffmpeg_ubuntu下配…

python代码制作数据集的测试和数据质量检测思路

前言 本文指的数据集为通用数据集&#xff0c;并不单是给机器学习领域使用。包含科研和工业领域需要自己制作数据集的。 首先&#xff0c;在制作大型数据集时&#xff0c;代码错误和数据问题可能会非常复杂。 前期逻辑总是简单的&#xff0c;库库一顿写&#xff0c;等排查的时…

Windows系统编程 - 进程遍历

文章目录 前言进程的遍历CreateToolhelp32SnapshotProcess32FirstProcess32Next进程遍历 总结 前言 各位师傅好&#xff0c;我是qmx_07&#xff0c;今天给大家讲解进程遍历的相关知识点 进程的遍历 快照&#xff1a;使用vmware虚拟机的时候&#xff0c;经常需要配置环境服务…

【GD32】(三) ISP基本使用

0 前言 有一块GD32的板子不知道为啥用着用着就下载不了程序了&#xff0c;没办法&#xff0c;只能另寻他法。作为STM32的平替&#xff0c;GD32的功能和STM32基本是一致的&#xff0c;所以也可以使用ISP来下载程序。于是就开始复活这块板子。 1 BOOT模式 对于熟悉STM32开发的人…

【SKFramework框架核心模块】3-2、音频管理模块

推荐阅读 CSDN主页GitHub开源地址Unity3D插件分享QQ群&#xff1a;398291828小红书小破站 大家好&#xff0c;我是佛系工程师☆恬静的小魔龙☆&#xff0c;不定时更新Unity开发技巧&#xff0c;觉得有用记得一键三连哦。 一、前言 【Unity3D框架】SKFramework框架完全教程《全…

开源项目-如何更好的参与开源项目开发

开源之谜-提升自我核心竞争力 一、寻找适合自己的开源项目二、像坐牢一样闭关修炼三、最后的实践 开源代码对所有人开放&#xff0c;开发者可以基于现有代码进行扩展和创新&#xff0c;而不是从零开始&#xff0c;参与开源项目可以提升自我的技术能力&#xff0c;丰富个人的经历…

利用c语言详细介绍下插入排序

插入排序&#xff0c;被称为直接插入排序。它的基本思想是将一个记录插入到已经排好序的有序表中&#xff0c;从而一个新的、记录数增 1 的有序表。 一、图文介绍 我们还是使用数组【10&#xff0c;5&#xff0c;3&#xff0c;20&#xff0c;1]&#xff0c;排序使用升序的方式&…

STL——string类常用接口说明

目录 一、string类的介绍 二、string类常用接口的使用说明 1.成员函数 ​编辑 2.迭代器 3.容量 一、string类的介绍 下面是string类的文档对string类的介绍 1.string类是表示字符序列的对象 2.标准字符串类通过类似于标准字符容器的接口为此类对象提供支持&#xff0c…

Excel的图表使用和导出准备

目的 导出Excel图表是很多软件要求的功能之一&#xff0c;那如何导出Excel图表呢&#xff1f;或者说如何使用Excel图表。 一种方法是软件生成图片&#xff0c;然后把图片写到Excel上&#xff0c;这种方式&#xff0c;因为格式种种原因&#xff0c;导出的图片不漂亮&#xff0c…

2024年亚太地区数学建模大赛A题-复杂场景下水下图像增强技术的研究

复杂场景下水下图像增强技术的研究 对于海洋勘探来说&#xff0c;清晰、高质量的水下图像是深海地形测量和海底资源调查的关键。然而&#xff0c;在复杂的水下环境中&#xff0c;由于光在水中传播过程中的吸收、散射等现象&#xff0c;导致图像质量下降&#xff0c;导致模糊、…

【数据分享】2024年我国省市县三级的住宿服务设施数量(8类住宿设施/Excel/Shp格式)

宾馆酒店、旅馆招待所等住宿服务设施的配置情况是一个城市公共基础设施完善程度的重要体现&#xff0c;一个城市住宿服务设施种类越丰富&#xff0c;数量越多&#xff0c;通常能表示这个城市的公共服务水平越高&#xff01; 本次我们为大家带来的是我国各省份、各地级市、各区…

一文学习Android系统核心服务ServiceManager

ServiceManager 是 Android 系统中核心的系统服务注册与发现机制&#xff0c;它在 Android Framework 层扮演服务注册中心的角色。它允许进程通过它注册、查询和使用系统服务&#xff0c;实现进程间通信 (IPC) 的基础架构。 ServiceManager 的作用 服务注册&#xff1a;应用程…

DMA理论篇

DMA理论篇 简介 传统的数据传输都是需要CPU来实现&#xff0c;从一个地方拷贝到另一个地方&#xff1b;而DMA(Direct Memory Access)则不完全依赖CPU&#xff0c;DMA更新芯片SOC的一个控制器&#xff0c;他可以控制数据从内存中传输到另一个地方(外设、soc其它模块)&#xff…