HashMap底层源码分析

目录

  • 一、知识点
  • 二、数据结构
  • 三、resize() 扩容方法
  • 四、putVal() 添加数据方法
  • 五、remove() 删除方法
  • 六、removeTreeNode() 退化链表方法


一、知识点

加载因子:
HashMap 的默认的加载因子: 0.75,用来限定阈值(用于控制 HashMap 的饱和度)
阈值 = 加载因子 * 数组长度
默认阈值: 16 * 0.75 = 12 (默认的初始容量:16)
在数组中存储元素数量,超过阈值,会引发数组扩容

Hash算法:
不是加密算法,不可逆
常见的哈希算法:MD4 、 MD5 、SHA1、SHA2、 SHA3

hashmap 的 hash值的计算:
// 不是哈希算法,仿照哈希算法思想
// 不够充分随机,但是速度快
// 要存储 key-value 数据,对 key 计算,得到一个 int 类型的 hash 值
// 如果kay值是null, 它对应的hash值是0
// 如果kay值不是null, 取key的hashCode 异或上  hashCode向right移动16位    
// hashCode 向低位移动16位, 让高位和低位取异或运算的目的, 就是希望高位和低位充分混合, 最终都能参加到取模预算中去 
(key == null) ? 0 : (h = key.hashCode()) ^ (h >>> 16);手动计算HashMap的hash值举例:
String str = "study";
int hash = str.hashCode();
hash = hash ^ (hash >>> 16);
// 将哈希值映射到有限的索引范围内
int index = hash % 底层数组长度;// hashCode(用于记录对象的位置)不一样, 那么hash值一定不一样, hashCode和hash值是一一对应关系
如果一个key-value数据在数组x下标位置, 当这个数组扩容, 扩容完成之后, 这个key-value数据要重新散列, 要么还在x位置, 要么在旧数组长度+x位置
因为默认的扩容机制是2

二、数据结构

class HashMap{Node[] table; // HashMap的底层数组int size;float loadFactor; // 加载因子float DEFAULT_LOAD_FACTOR = 0.75f;	// 默认加载因子int threshold; // 阈值// ......
}

// 存储到HashMap底层Node数组中结点的类型:
// 添加key-value数据,实际上存储到对应经过计算 hash 值取模得到的下标的位置
class Node<K,V>  {final int hash;		// 扩容数组后,原本存在某个位置的元素可能改变位置final K key;V value;Node<K,V> next;    	// HashMap底层数组中是链表
}

三、resize() 扩容方法

给定数组长度点和 ArrayDeque 中的 initialCapacity 类似,只是 initialCapacity 是大于,没有等于
在构造方法里给 HashMap 一个长度, 它会把这个长度变成一个大于等于这个值的最小的2的幂值作为底层数组的长度
HashMap 的底层结构一直会保持2的幂值

final Node<K,V>[] resize() {// 旧数组Node<K,V>[] oldTab = table;// 旧数组长度int oldCap = (oldTab == null) ? 0 : oldTab.length;// 旧阈值int oldThr = threshold;// 新数组长度,新阙值int newCap, newThr = 0;if (oldCap > 0) { // 场景二:不是第一次添加数据if (oldCap >= MAXIMUM_CAPACITY) {threshold = Integer.MAX_VALUE;return oldTab;}else if ((newCap = oldCap << 1) < MAXIMUM_CAPACITY &&oldCap >= DEFAULT_INITIAL_CAPACITY)newThr = oldThr << 1; // 默认的扩容机制:2倍}else if (oldThr > 0) // 场景一:第一次添加数据扩容,阙值不为0,数组为0// 比如调用构造方法 new HashMap(initCap,loadFactor); new HashMap(initCap); 等newCap = oldThr;else { // 场景一:第一次添加数据扩容,oldTab = null,oldCap = 0,oldThr = 0  // DEFAULT_INITIAL_CAPACITY = 1 << 4;// newCap(新容量) =  16newCap = DEFAULT_INITIAL_CAPACITY;// newThr(新阈值) = 0.75 * 16 = 12newThr = (int)(DEFAULT_LOAD_FACTOR * DEFAULT_INITIAL_CAPACITY);}// 新阙值未赋值,如上面的if-else if不成立if (newThr == 0) {float ft = (float)newCap * loadFactor;newThr = (newCap < MAXIMUM_CAPACITY && ft < (float)MAXIMUM_CAPACITY ?(int)ft : Integer.MAX_VALUE);}threshold = newThr;Node<K,V>[] newTab = (Node<K,V>[])new Node[newCap];table = newTab;// 上面的代码是在创建新的数组// 这里把旧数组数据转移到新数组中if (oldTab != null) {...}return newTab;
}

四、putVal() 添加数据方法

public V put(K key, V value) {return putVal(hash(key), key, value, false, true);
}final V putVal(int hash, K key, V value, boolean onlyIfAbsent, boolean evict) {Node<K, V>[] tab;Node<K, V> p;int n, i;// 扩容if ((tab = table) == null || (n = tab.length) == 0)n = (tab = resize()).length;// (n - 1) & hash : hash值和数组长度取模// 因为数组长度是2的幂值,所以可以用&操作// i 取模之后的下标// p 指向key-value数据经过取模之后的下标位置if ((p = tab[i = (n - 1) & hash]) == null)tab[i] = newNode(hash, key, value, null);else {Node<K, V> e; // e指向需要修改的元素K k;// 要添加的key的位置,不为空,比较key// 先比较hash值,再看两个key是否直接相等或者相equalsif (p.hash == hash &&((k = p.key) == key || (key != null && key.equals(k))))e = p;// 判断 此处是不是树else if (p instanceof TreeNode)e = ((TreeNode<K, V>) p).putTreeVal(this, tab, hash, key, value);else {// 这个下标位置存储的不是树,是链表// 初始:p指向数组的“第一个元素”for (int binCount = 0; ; ++binCount) {// 注意:这里 e = p.next,e指向链表的第二个元素(数组中的数组为第一个元素),此时 binCount == 0if (( e = p.next) == null) {p.next = newNode(hash, key, value, null);                                           // 当链表长度超过8达到9个数据的时候会进行红黑树的转化(阈值是8)// HashMap中出现红黑树的概率很低if (binCount >= TREEIFY_THRESHOLD - 1) // >= 7// 转化树,不一定会构造树(发生树的转化,但不一定会将链表转换成红黑树)treeifyBin(tab, hash);break;}// 链表中有重复元素, key相同,新值覆盖旧值if (e.hash == hash &&((k = e.key) == key || (key != null && key.equals(k))))break;p = e;}}if (e != null) { // 新值覆盖旧值V oldValue = e.value;if (!onlyIfAbsent || oldValue == null)e.value = value;afterNodeAccess(e);return oldValue;}}// ...return null;
}

转化树的源码:
final void treeifyBin(Node<K,V>[] tab, int hash) {int n, index; Node<K,V> e;
当底层数组数为空,或者底层数组的长度 < MIN_TREEIFY_CAPACITY (初始值为64) 时,会选择扩容,而不是创建树
因为底层数组的长度是2的幂次值,< 64,即底层数组的长度最大为 320.75 的加载因子,即底层数组允许存储的数据的最大值是 32*0.75 = 24,这时即使某一个下标位置已经产生长度为9的链表, 是选择扩容, 而非转化为红黑树resize();if (tab == null || (n = tab.length) < MIN_TREEIFY_CAPACITY)注意:
如果我们使用某些特殊的类型来充当key, 要根据上面这个条件判断是否需要重写hashCode和equals方法

在这里插入图片描述


五、remove() 删除方法

public V remove(Object key) {Node<K,V> e;return (e = removeNode(hash(key), key, null, false, true)) == null ?null : e.value;
}final Node<K,V> removeNode(int hash, Object key, Object value,boolean matchValue, boolean movable) {Node<K,V>[] tab; Node<K,V> p; int n, index;if ((tab = table) != null && (n = tab.length) > 0 &&(p = tab[index = (n - 1) & hash]) != null) {Node<K,V> node = null, e; K k; V v;if (p.hash == hash &&((k = p.key) == key || (key != null && key.equals(k))))node = p;else if ((e = p.next) != null) {if (p instanceof TreeNode)node = ((TreeNode<K,V>)p).getTreeNode(hash, key);else {do {if (e.hash == hash &&((k = e.key) == key ||(key != null && key.equals(k)))) {node = e;break;}p = e;} while ((e = e.next) != null);}}if (node != null && (!matchValue || (v = node.value) == value ||(value != null && value.equals(v)))) {if (node instanceof TreeNode)((TreeNode<K,V>)node).removeTreeNode(this, tab, movable);else if (node == p)tab[index] = node.next;elsep.next = node.next;++modCount;--size;afterNodeRemoval(node);return node;}}return null;
}// 注:也可使用map.clear();将map中的所有元素全部删光

六、removeTreeNode() 退化链表方法

情况一:
移除元素 remove() 操作:
当红黑树(根节点/根节点的左右结点/根节点的左结点的左结点)这四个结点有任意一个不存在, 就要由红黑树转化为链表  
在final void removeTreeNode(HashMap<K,V> map, Node<K,V>[] tab,boolean movable) {...}中
关键代码:            
if (root == null || root.right == null ||(rl = root.left) == null || rl.left == null) {tab[index] = first.untreeify(map);  // too smallreturn;
}情形二:
底层数组的扩容resize()方法,导致树拆成两部分,树中的结点过少,转化成链表
判断节点类型,如果是链表,则将链表拆分,如果是TreeNode,则执行TreeNode的split方法分割红黑树
if (lc <= UNTREEIFY_THRESHOLD) // 低位;UNTREEIFY_THRESHOLD = 6tab[index] = loHead.untreeify(map);
if (hc <= UNTREEIFY_THRESHOLD) // 高位tab[index + bit] = hiHead.untreeify(map);

为什么链表转化为红黑树阈值是8, 红黑树转化为链表阈值是6 ?
担心如果阈值设为一样, 添加和删除可能导致红黑树和链表间的反复转化

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

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

相关文章

红酒保存中的摆放方式:倾斜瓶身的重要性

在探讨云仓酒庄雷盛红酒的保存方法时&#xff0c;我们不得不提及一个关键的细节&#xff1a;瓶身的倾斜。许多人可能认为红酒的保存方式仅仅是温度控制和存储环境的湿度问题&#xff0c;然而实际上&#xff0c;摆放方式同样至关重要。雷盛红酒在保存过程中&#xff0c;需要一个…

安川机器人MA1440减速机维修方法

一、安川机械臂减速器维修方法 1. 齿轮磨损维修 对于轻微磨损的齿轮&#xff0c;可以通过重新调整啮合间隙来恢复性能。对于严重磨损的齿轮&#xff0c;需要更换新安川MA1440机械手齿轮箱齿轮。 2. 轴承损坏维修 对于损坏的轴承&#xff0c;需要更换新的轴承。在更换过程中&…

省市县选择三级联动(使用高德API实现)

省市县选择如果自己实现是比较麻烦的&#xff0c;最近发现可以使用高德实现省市县联动选择&#xff0c;实现后来记录一下供大家参考。 文章目录 最终效果&#xff1a;一、准备工作二、完整页面代码 最终效果&#xff1a; 实现单次点击获取省市县名称&#xff0c;选择完成后返回…

web端即时通信技术

web端即时通信技术 对于IM/消息推送这类即时通讯系统而言&#xff0c;系统的关键就是“实时通信”能力。所谓实时通信有以下两层含义 客户端可以主动向服务端发送信息。 当服务端内容发生变化时&#xff0c;服务端可以实时通知客户端。 HTTP局限 Http是客户端/服务器模式中…

免费分享:1901-2020全球气候数据集(附下载办法)

长期的全球其后数据不仅能够揭示长期的气候趋势&#xff0c;还为农业、水资源管理、公共卫生等多个领域的决策提供科学依据&#xff0c;对于推动可持续发展具有重要意义。 数据集简介 CRU TS&#xff08;Climatic Research Unit gridded Time Series&#xff09;数据集&#…

宽睿数字平台兼容TDengine 等多种数据库,提供行情解决方案

小T导读&#xff1a;最近&#xff0c;涛思数据与宽睿金融宣布了一项重要合作。在此之前&#xff0c;宽睿金融对 TDengine 进行了性能测试&#xff0c;并根据测试报告的结果&#xff0c;决定将 TDengine 接入宽睿数字平台&#xff0c;以提升高密度行情处理效率。本文将详细介绍此…

百元开放式耳机性价比推荐,全网5款爆火产品实测!

耳机作为我们日常生活中不可或缺的一部分&#xff0c;它的性能、配置及形态也在不停的优化。目前开放式耳机是耳机中最为热门的一款产品&#xff0c;以其不入耳的的设计迅速占领耳机市场&#xff0c;最主要是因为自身佩戴的舒适度有大幅度提升。人们更加注重生活的质量&#xf…

Apollo9.0 PNC源码学习之Control模块(五)—— 基于LQR的横向控制

前面文章&#xff1a; Apollo9.0 PNC源码学习之Control模块&#xff08;一&#xff09; Apollo9.0 PNC源码学习之Control模块&#xff08;二&#xff09; Apollo9.0 PNC源码学习之Control模块&#xff08;三&#xff09; Apollo9.0 PNC源码学习之Control模块&#xff08;四&…

通信原理抽样定理和PAM调制解调硬件实验

一、实验目的 1. 加深理解抽样定理&#xff1b; 2. 加深理解脉冲幅度调制的原理。 二、实验内容 1. 观测PAM平顶抽样波形&#xff1b; 2. 观测PAM自然抽样波形及解码后波形。 三、实验器材 1. 双踪示波器&#xff1b; 2. 通信原理实验箱信号源模块、①号模块。 四、实…

Stable Diffusion学习指南【图生图篇】

一、图生图功能初识 1.1 传统意义上的喂参考图 我们都知道&#xff0c;模型在运算时是根据我们提供的提示内容来确定绘图方向&#xff0c;如果没有提示信息&#xff0c;模型只能根据此前的学习经验来自行发挥。在之前的文生图篇&#xff0c;我们介绍了如何通过提示词来控制图…

Centos系统yum安装mysql数据库

安装之前需要将系统自带的mariadb-libs软件包删除。 检查是否存在mariadb-libs包。 yum list installed|grep mariadb-libs 删除mariadb-libs包 yum -y remove mariadb-libs 声明&#xff1a; 系统&#xff1a;CentOS-7-x86_64-DVD-2009 安装为最小化安装&#xff0c;没…

在IDEA中连接数据库

1.点击侧边栏的Database 2.选中你要连接的数据库类型 3.输入用户名、密码&#xff0c;测试连接&#xff0c;没有驱动的话先下载下驱动 4.发现没有数据库&#xff0c;点击1 of 14&#xff0c;选择All schemas&#xff0c;即可显示全部数据库 5.执行sql 如果对您有帮助的话记…

【ASE】笔记总结

这里写自定义目录标题 ASE 介绍ASE 的安装文件格式的转化命令行操作图形界面操作Python代码操作[2]支持的文件转化格式 ASE&#xff08;三&#xff09;&#xff1a;图形界面查看/建立/修改结构图形界面召唤查看结构 python代码查看/建立结构读写、查看结构建立结构分子 周期性体…

新创作活动,The Sandbox 宣布第二届 “创作者挑战”!

是时候展示你的实力了&#xff01; 继首届 “创作者挑战” 成功举办之后&#xff0c;The Sandbox 很高兴推出第二届 "创作者挑战"&#xff0c;邀请创作者再次释放他们的创造力和创业精神。本次活动从 2024 年 6 月 24 日开始至 8 月 26 日结束&#xff0c;为期 10 …

Ant-design-vue开源项目介绍、应用场景、组件有哪些

文章目录 一、Ant-design-vue项目介绍二、Ant-design-vue项目特点三、Ant-design-vue应用场景四、Ant-design-vue有哪些组件五、Ant-design-vue案例代码1. 后台管理系统登录页面的例子2. Table组件使用案例 开源项目地址 一、Ant-design-vue项目介绍 Ant-design-vue 是一个基于…

第104天: 权限提升-Linux 系统环境变量定时任务权限配置不当MDUT 自动化

目录 案例一&#xff1a;Linux-环境变量文件配合 SUID-本地 案例二&#xff1a;Linux-定时任务打包配合 SUID-本地 案例三&#xff1a;Linux-定时任务文件权限配置不当-WEB&本地 案例四&#xff1a;Linux-第三方软件 MYSQL 数据库提权-WEB&本地 隧道出网 手工提权…

Science Online《科学周刊》文献在家如何查找下载

今天有位同学自己学校没有Science《科学》数据库&#xff0c;求助几篇该数据库文献&#xff0c;小编就把自己在家下载文献的方法分享给了这位同学&#xff0c;同时也分享给其他有需要文献下载但找不到资源的朋友。本文以下载Science《科学》文献为例来演示一下在家获取文献的过…

32T存储删除视频的恢复方法

由于存储技术的发展和普及目前很多行业都开始使用小型存储&#xff0c;NAS可以通过网络进行数据上传和读取&#xff0c;使用极为方便。但是由于NAS设备容量较大且碎片较多&#xff0c;所以此类设备删除或者格式后恢复难度是比较大的&#xff0c;下边我们来分享下32T存储的恢复方…

opencv安装笔记 各种平台

目录 python安装opencv-python c 麒麟arm系统安装和用法 python安装opencv-python pypi上搜索 Search results PyPI 现在安装是一个版本&#xff0c;大于3.6都可以安装 c 麒麟arm系统安装和用法 参考&#xff1a; ffmpeg rknn麒麟系统 安装 opencv_ffmpeg4 解码示例-CSDN…

【Gitlab】访问默认PostgreSQL数据库

本地访问PostgreSQL gitlab有可以直接访问内部PostgreSQL的命令 sudo gitlab-rails dbconsole # 或者 sudo gitlab-psql -d gitlabhq_production效果截图 常用SQL # 查看用户状态 select id,name,email,state,last_sign_in_at,updated_at,last_credential_check_at,last_act…