常见面试题-HashMap源码

了解 HashMap 源码吗?

参考文章:https://juejin.cn/post/6844903682664824845

https://blog.51cto.com/u_15344989/3655921

以下均为 jdk1.8 的 HashMap 讲解

首先,HashMap 的底层结构了解吗?

底层结构为:数组 + 链表 + 红黑树

什么时候链表会转换为红黑树呢?

当一个位置上哈希冲突过多时,会导致数组中该位置上的链表太长,链表的查询时间复杂度是O(N),即查询代价随着链表长度线性增长,那么在 HashMap 中就通过 TREEIFY_THRESHOLD=8 来控制链表的长度,当链表的长度大于 8 时并且数组长度大于 64 时,就将链表转换为红黑树

这里在冲突插入链表时,使用的是尾插法,会顺着链表进行判断,当遍历到链表最后一个节点时,并判断链表长度是否需要转为红黑树,之后再通过尾插法,插入在最后一个节点的后边

扩展:jdk8 之前是头插法,但是 jdk8 改为了尾插法,这是为什么呢?为什么 jdk8 之前要采用头插法呢?

jdk1.7 使用头插法的一种说法是,利用到了缓存的时间局部性,即最近访问过的数据,下次大概率还会进行访问,因此把刚刚访问的数据放在链表头,可以减少查询链表的次数

jdk1.7 中的头插法是存在问题的,在并发的情况下,插入元素导致扩容,在扩容时,会改变链表中元素原本的顺序,因此会导致链表成环的问题

那么 jdk8 之后改为了尾插法,保留了元素的插入顺序,在并发情况下就不会导致链表成环了,但是 HashMap 本来就不是线程安全的,如果需要保证线程安全,使用 ConcurrentHashMap 就好了!

如何计算插入节点在数组中需要存储的下标呢?

计算下标是先计算出 key 的 hash 值,在将 hash 值对数组长度进行取模,拿到在数组中存放的位置

计算 hash 值代码如下:

(h = key.hashCode()) ^ (h >>> 16)

首先拿到 key 的 hashCode,将 hashCode 和 h >>> 16 进行异或运算,此时计算出来 key 的哈希值 hash,这里计算 哈希值 时,因为在计算数组中的下标时,会让 hash 值对数组长度取模,一般数组长度不会太大,导致 hash 值的高 16 位参与不到运算,因此让 hashCode 在与 hashCode >>> 16 进行异或操作,让 hashCode 的高 16 位也可以参与到下标的计算中去,这样计算出的下标更不容易冲突

这里面试官问了 hashCode 一定是 32 位吗?当时没反应过来,其实一定是 32 位的,因为 hashCode 是 int 类型,这里说的 32 位其实是二进制中是 32 位,int 类型是 4B = 32bit

那么在数组中的下标为:hash & (n-1) 也就是让 hash 值对数组长度进行取模,从而拿到在数组中的下标。(这里 hash & (n-1) == hash % n,hash 值和 n-1 进行与操作其实就是使用二进制运算进行取模)

这里举个取模运算的例子:

比如数组长度为 8,计算出来的 hash 值为 19,那么

19 & (8 - 1) = 10011 & 00111(二进制) = 00011(二进制) = 3

19 % 8 = 3

HashMap 中如何进行扩容的呢?

当 HashMap 中的元素个数超过数组长度 * loadFactor(负载因子)时,就会进行数组扩容,负载因子默认为 0.75,数组大小默认为 16,因此默认是 HashMap 中的元素个数超过 (16 * 0.75 = 12) 时,就会将数组的大小扩展为原来的一倍,即 32,之后再重新计算数组的下标,这异步操作是比较耗费性能的,所以如果可以预知 HashMap 中元素的个数,可以提前设置容量,避免频繁的扩容

在 HashMap 扩容时,即在 resize() 方法中,如果数组中某个位置上的链表有多个元素,那么我们如果对整条链表上的元素都重新计算下标是非常耗时的操作,因此在 HashMap 中进行了优化,HashMap 每次扩容都是原来容量的 2 倍,那么一条链表上的数据在扩容之后,这一条链表上的数据要么在原来位置上,要么在原来位置+原来数组长度上,这样就不需要再对这一条链表上的元素重新计算下标了,下边来解释一下为什么这一条链表扩容后的位置只可能是这两种情况:

因为每一次扩容都是容量翻倍,在下标计算中 (n-1) & hash 值,n 每次扩容都会增大一倍,那么 (n-1) 在高位就会多一个 1,比如(可能写的有些啰嗦,主要是这一段用文字不太好描述,耐心看一下就可以看懂):

假如说我们插入一个 key="zqy" 时,从 16 扩容为 32 ,我们来看一下扩容前后的如何计算下标:

  • n 为 16 时,n-1 只有 4 个 1
  • n 为 32 时,n-1 有 5 个 1,在高位多出来了一个 1

在这里插入图片描述

下标的计算公式为 (n-1)&hash,n 每次都是扩容1倍,也就是 n-1 的二进制中会在高位多一个 1,那么如果 hash 值在多出来的 1 这一位上为 1,那么下标计算之后就比原下标多了一个 oldCap,如果 hash 值在多出来的 1 这一位上为 0,那么就不会对下标计算有影响,新下标还是等于原下标

那么怎么判断在多出来的这一个 1 的位置上,hash 值是否为 1 呢?只需要让 hash & oldCap 即可,对上图来说,在扩容之后,当 n 为 32 时, n-1 中会多出来标位红色的1,那么需要判断的就是"zqy"的 hash 值中绿色的位置那一位是否为1(通过 hash&oldCap 来判断),如果为1,新下标=原下标+oldCap;如果为 0,新下标=原下标

上边说的源码位置如下图,下边为 resize() 方法中的部分代码,优化位置在 738742 行,在 715 行开始的 else 语句中,针对的就是原数组的位置上的链表有多个元素,在 721 行判断,如果 hash & oldCap 是 0 的话,表示该链表上的元素的新下标为原下标;如果是 1,表示新下标=原下标+原数组长度

在这里插入图片描述

HashMap 在链表长度达到 8 之后一定会转为红黑树吗?如何转为红黑树呢?

HashMap 会在数组长度大于 64 并且链表长度大于 8 才会将链表转为红黑树

在下边这个转成红黑树的方法中,757 行就判断了 tab.length 也就是数组的长度,如果小于 64,就进行扩容,不会将链表转成红黑树

如果需要转换成红黑树,就进入到 759 行的 if 判断,先将链表的第一个节点赋值为 e,之后将 e 转为 TreeNode,并且将转换后的树节点给串成一个新的链表,hd 为链表头,tl 为链表尾,当将链表所有节点转为 TreeNode 之后,在 771 行使用转换后的双向链表替代原来位置上的单链表,之后再 772 行调用 treeify() ,该方法就是将链表中的元素一个一个插入到树中

在这里插入图片描述

HashMap不是线程安全的,那么举一个不安全的例子吧?

我们可以来分析一下,在多线程情况下,那么一般是多个线程修改同一个 HashMap 所导致的线程不安全,那么也就是 put() 操作中,会造成线程不安全了,那么我们看下边 putVal() 方法,来分析一下在哪里会造成线程不安全:

假如初始时,HashMap 为空,此时线程 A 进到 630 行的 if 判断,为 true,当线程 A 准备执行 631 行时,此时线程 B 进入在 630 行 if 判断发现也为 true,于是也进来了,在 631 行插入了节点,此时线程 B 执行完毕,线程 A 继续执行 631 行,就会出现线程 A 插入节点将线程 B 插入的节点覆盖的情况

在这里插入图片描述

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

如若内容造成侵权/违法违规/事实不符,请联系我们进行投诉反馈qq邮箱809451989@qq.com,一经查实,立即删除!

相关文章

基于深度学习的活体人脸识别检测算法matlab仿真

目录 1.算法运行效果图预览 2.算法运行软件版本 3.部分核心程序 4.算法理论概述 4.1. 活体人脸识别检测算法概述 4.2. 深度学习在活体人脸识别检测中的应用 4.3. 算法流程 5.算法完整程序工程 1.算法运行效果图预览 2.算法运行软件版本 MATLAB2022a 3.部分核心程序 …

分形图案是什么?fpmarkets这样进入市场

分形图案的构造相对简单。市场在某个时间段内,会呈现单向的变动,要么持续上涨,要么持续下跌。观察这种趋势,并预测市场将呈现上涨态势后,过了一段时间,当所有有意向的买家都已经完成购买行为(即在价格上涨过…

CTFd-Web题目动态flag

CTFd-Web题目动态flag 1. dockerhub注册2. dockerfile编写3. 上传到docker仓库4. 靶场配置5. 动态flag实现 1. dockerhub注册 想要把我们的web题目容器上传到docker仓库中,我们需要dockerhub官网注册一个账号,网址如下 https://hub.docker.com/2. dock…

消息中间件概述

概述 消息队列已经逐渐成为企业IT系统内部通信的核心手段。它具有低耦合、可靠投递、广播、流量控制、最终一致性等一系列功能,成为异步RPC的主要手段之一。当今市面上有很多主流的消息中间件,如ActiveMQ、RabbitMQ,Kafka,还有阿里…

MySQL和Oracle的区别有什么

1、mysql与oracle都是关系型数据库,应用于各种平台。 mysql开源免费的,而oracle则是收费的,并且价格非常高。 2、管理工具上 mysql的管理工具较少,在Linux下的管理工具的安装有时需要安装额外的包(phpmyadmin&#…

Linux 无名管道实现文件复制

无名管道 通过一个管道(假象)进行传输数据,但是这个管道的传输方式是单工(半双工)的,就是这个管道允许进行发送和接受数据,不过不能同时进行。 创建无名管道 这里用到一个pipe(&…

C语言求0—7所能组成的奇数个数

完整代码&#xff1a; // 求0—7所能组成的奇数个数 //根据题意&#xff0c;应该是没有重复数字的&#xff0c;所以最大只能为八位数 //如果可以重复的话&#xff0c;那么位数就限制不了&#xff0c;然后奇数的个数就是无穷大了 #include <stdio.h>int main() {int coun…

怎么用领英开发客户?分享领英开发客户的方法和技巧

对于绝大多数外贸业务员来说&#xff0c;领英(LinkedIn)是一个非常重要且有效的客户开发渠道。在领英这个平台&#xff0c;如果你掌握了开发客户的方法&#xff0c;那么营销推广产品或服务的终极目标就有很大可能的实现&#xff01;其实真正上手并不难&#xff0c;因为平台内有…

口袋参谋:一键查询任意买家旺旺号,规避被降权风险!

​ 对于淘宝天猫的卖家来说&#xff0c;查买家旺旺号是维护淘宝卖家销售权益的一种途径。 卖家通过查买家的旺旺号&#xff0c;从而得知买家的账号信息、买家信誉以及中差评等内容&#xff0c;减少淘宝卖家受骗上当的机率。 【查降权号】功能&#xff1a; 针对淘宝订单可一键查…

2024年山东省职业院校技能大赛中职组“网络安全”赛项竞赛试题-A

2024年山东省职业院校技能大赛中职组 “网络安全”赛项竞赛试题-A 一、竞赛时间 总计&#xff1a;360分钟 二、竞赛阶段 竞赛阶段 任务阶段 竞赛任务 竞赛时间 分值 A、B模块 A-1 登录安全加固 180分钟 200分 A-2 本地安全策略设置 A-3 流量完整性保护 A-4 …

【C++】入门二

下面我们说一下缺省参数&#xff0c;那么什么是缺省参数呢&#xff1f;就是说在定义或者声明函数时给形参赋予一个确定的值&#xff08;也叫缺省值&#xff09;&#xff0c;那么当我们调用这个函数的时候&#xff0c;就可以不传值也可以传值&#xff0c;传值的话缺省值就没作用…

什么是Selenium?如何使用Selenium进行自动化测试?

什么是 Selenium&#xff1f; Selenium 是一种开源工具&#xff0c;用于在 Web 浏览器上执行自动化测试&#xff08;使用任何 Web 浏览器进行 Web 应用程序测试&#xff09;。   等等&#xff0c;先别激动&#xff0c;让我再次重申一下&#xff0c;Selenium 仅可以测试Web应用…

微服务实战系列之Token

前言 什么是“Token”&#xff1f; 它是服务端生成的一串字符串&#xff0c;以作客户端进行请求的一个令牌&#xff0c;当第一次登录后&#xff0c;服务器生成一个Token便返回给客户端&#xff1b;以后客户端只携带此Token请求数据即可。 简言之&#xff0c;Token其实就是用户身…

大型语言模型中的幻觉研究综述:原理、分类、挑战和未决问题11.15+11.16+11.17

大型语言模型中的幻觉研究综述&#xff1a;原理、分类、挑战和未决问题11.15 摘要1 引言2 定义2.1 LLM2.3 大语言模型中的幻觉 3 幻觉的原因3.1 数据的幻觉3.1.1 有缺陷的数据源3.1.2 较差的数据利用率3.1.3 摘要 3.2 来自训练的幻觉3.2.1训练前的幻觉3.2.2来自对齐的幻觉3.2.3…

如何在latex中高亮文本

导入soul 包可以使用高亮功能 在文本中插入 \hl{} 即可 导入color 包可以使用颜色功能 color 也可以替换成 xcolor \documentclass{report} \usepackage{xcolor,soul} \begin{document}\textcolor{red}{Text}\hl{Text} \hl{\textbf{Text}} \textbf{\textcolor{red}{\hl{Text}…

LeetCode题 338比特位计数,20有效的括号,415字符串相加

目录 338比特位计数 题目要求&#xff1a; 解题思路&#xff1a; 1、暴力穷举 代码&#xff1a; 2、N&&#xff08;N - 1&#xff09;公式求解 代码&#xff1a; 3、奇偶数性质解法&#xff1a; 代码&#xff1a; 20有效的括号 题目要求&#xff1a; 解题思路 …

CTF-PWN环境搭建手册

工欲善其事必先利其器&#xff0c;作为一名CTF的pwn手&#xff0c;一定要有自己的专用解题环境。本文将详细记录kali下的pwn解题环境的安装过程&#xff0c;B站也会配备配套视频。 安装前的准备工作 虚拟机环境 VMware WorkStation VM版本安装教程 1. 下载Kali的VM虚拟机文件…

Java内存区域速览

文章目录 JVM的组成加载字节码流程 运行时数据区-总览1. 程序计数器2. 虚拟机栈栈帧栈的运行原理 3. 本地方法栈4. 堆内存(Java Heap虚拟机对堆 的划分1. 年轻代&#xff08;Young Generation&#xff09;&#xff1a;2. 老年代&#xff08;Old Generation&#xff09;&#xf…

变长子网划分问题的二叉树解法

计网的变长子网划分、计组的变长操作码划分、数据结构的哈夫曼编码&#xff0c;都是前缀编码的本质&#xff08;变长操作码的二叉树解法我还在琢磨中&#xff09; 【二叉树解法】每条从叶结点到根节点的路径上有且只有一个被分配的结点&#xff1a; 【例】现将一个IP网络划分成…

第七部分:Maven(项目管理工具)

目录 Maven简介 7.1&#xff1a;为什么学习Maven&#xff1f; 7.1.1、Maven是一个依赖管理工具 7.1.2&#xff1a;Maven是一个构建工具 7.1.3&#xff1a;结论 7.2&#xff1a;Maven介绍 7.3&#xff1a;Maven的优点 Maven安装和配置 7.4&#xff1a;安装教程及环境配置 …