HashMap中hash方法的作用(详解)

news2024/9/25 13:16:57

首先,hash方法用来干什么?

在搞清楚原理之前,我们先站在巨人的肩膀浅浅了解一下hash方法的本质作用。

实质上,它的作用很朴素,就是用key值通过某种方式计算出一个hash码

而且这个hash码我们后面要用来计算key存在底层数组的下标,所以它必须保持一定的随机性,让计算出的数组元素下标更加均衡分布,减少碰撞,其实就是更大程度上的避免hash冲突。

注:保持随机性这一性质,只有在hash方法计算hash值这一步会有所体现,后面的取模运算只是单独的为了取到余数,与保证随机性没有关系

Hash方法是什么?

这里先直观的给出它的源码:

参数 key 需要计算哈希码的键值。
key == null ? 0 : (h = key.hashCode()) ^ (h >>> 16) :这是一个三目运算符,看似复杂,其实很好理解。
逻辑就是:如果键值为null,则哈希码为 0 (依旧是说如果键为 null ,则存放在第一个位置);否则,通过调用 hashCode() 方 法获取键的哈希码,并将其与右移16 位的哈希码进行异或运算。
^ 运算符:异或运算符是 Java 中的一种位运算符,它用于将两个数的二进制位进行比较,如果相同则为 0,不同则为 1
 >>> 16 将哈希码向右移动 16 位,相当于将原来的哈希码分成了两个 16 位的部分。最终返回的是经过异或运算后得到的哈希码值。

这短短的一行代码,汇聚不少计算机巨佬们的聪明才智。

理论上,哈希值(即hashcode方法返回的值)是一个 int 类型,大家都知道int型在java中占4个字节,即32位,范围从 -2147483648 2147483648 。前后加起来大概 40 亿的映射空间,只要哈希值映射得比较均匀松散,一般是不会出现哈希碰撞(哈希冲突会降低 HashMap 的效率)。
但问题是一个 40 亿长度的数组,内存是放不下的。 HashMap 扩容之前的数组初始大小只有 16 ,所以这个哈希值是不能直接拿来用的,用之前要和数组的长度做取模运算(前文提到的 (n - 1) & hash ),用得到的余数来访问数组下标才行。

对h ^ h>>>16如何理解?

上面的h其实就是我们调用key的hashcode()方法得到的hashcode值,我们将它右移16位(因为是int型,所以总共是32位),前面16位补0,然后我们让它与原本的hashcode值进行异或,因为异或的逻辑是不同为1,相当于就是前16位与后16位进行会进行一次异或,占据最终返回的hash的后16位,然后前16位与进行补位的16个0进行异或,占据最终返回的hash的前16位,得到最终return的hash码。

上面的流程可以参考下面的图进行思考:

 

这样大家可能会比较疑惑,这么翻来覆去是为了啥呀,其实都是为了一个:hash码的随机性,目的还是为了避免hash冲突,毕竟hash冲突会降低HashMap的效率。

综上所述, hash 方法是用来做哈希值优化的 ,把哈希值右移 16 位,也就正好是自己长度的一半,之后与原哈希值做异或运算,这样就混合了原哈希值中的高位和低位,增大了随机性。

 

计算好了hash值,然后我们怎么使用hash值计算下标?

我们使用(n - 1)& hash的方式获取下标;(其实就是取模运算)

大家可能会比较好奇,要进行取模运算难道不该用% 吗?为什么要用位运算 &呢

这是因为%虽然确实可以实现,但是实际效率却还是不如&

并且我们的&能够取代%,是要有一个前提条件的:

只有当 b 2 n 次方时,才存在这样一个公式:a % b = a & (b-1)

我们可以对其进行一个验证:

我们来验证一下,假如 a = 14 b = 8 ,也就是 2^3 n=3
14%8 (余数为 6), 14 的二进制为 1110 8 的二进制 1000 8-1 = 7 7 的二进制为 0111 1110&0111=0110 ,也就是 0 * 2^0+1 * 2^1+1 * 2^2+0 * 2 ^3=0+2+4+0=6 14%8 刚好也等于 6
害,计算机就是这么讲道理,没办法
也就是说,只有数组长度是2的n次方时,使用&才会取余成功,这也是为什么,我们对hashmap进行扩容时,必须是2倍扩容了
为什么会这么巧,刚好二者就相同了呢,这与一个低位掩码的概念有关,这里我们先不细讲。

顺着上一个问题的图,我们再看这个步骤进行理解:

 

知道了hash方法的原理以及并将其计算出来,我们还需要知道:

 hash方法在哪里被用到?

1.首先当然是我们最经典的put方法 ,后面的putval会通过hash值来计算下标

2.其次是我们使用get方法获取元素时,会调用getNode方法,其中用到hash值

小结

hash 方法的主要作用是将 key 的 hashCode 值进行处理,得到最终的哈希值。由于 key 的hashCode 值是 不确定的,可能会出现哈希冲突,因此需要将哈希值通过一定的算法映射到HashMap 的实际存储位置上。
hash 方法的原理是,先获取 key 对象的 hashCode 值,然后将其高位与低位进行异或操作,得到一个新的哈希值。为什么要进行异或操作呢?因为对于 hashCode 的高位和低位,它们的分布是比较均匀的,如果只是简单地将它们加起来或者进行位运算,容易出现哈希冲突,而异或操作可以避免这个问题。然后将新的哈希值取模(mod),得到一个实际的存储位置。这个取模操作的目的是将哈希值映射到桶(Bucket)的索引上,桶是 HashMap 中的一个数组,每个桶中会存储着一个链表(或者红黑树),装载哈希值相同的键值对(没有相同哈希值的话就只存储一个键值对)。
总的来说,HashMap 的 hash 方法就是将 key 对象的 hashCode 值进行处理,得到最终的哈希值,并通过一定的算法映射到实际的存储位置上。这个过程决定了 HashMap 内部键值对的查找效率。

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

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

相关文章

数据链路层是如何传递数据的

数据链路层是如何传递数据的 数据链路层功能概述封装成帧透明传输差错控制 数据链路层功能概述 数据链路层的主要作用就是加强物理层传输原始比特流的功能。其负责将物理层提供的可能出错的物理连接,改造成逻辑上无差错的数据链路。 数据链路层包括三个基本问题&a…

ICML 2023 | 拓展机器学习的边界

编者按:如今,机器学习已成为人类未来发展的焦点领域,如何进一步拓展机器学习技术和理论的边界,是一个极富挑战性的重要话题。7月23日至29日,第四十届国际机器学习大会 ICML 2023 在美国夏威夷举行。该大会是由国际机器…

Go语言学习笔记(狂神说)

Go语言学习笔记(狂神说) 视频地址:https://www.bilibili.com/video/BV1ae41157o9 1、聊聊Go语言的历史 聊聊Go语言的历史-KuangStudy-文章 2、Go语言能做什么 下面列举的是原生使用Go语言进行开发的部分项目。 Docker Docker 是一种操作…

【雕爷学编程】Arduino动手做(99)---8X32 LED点阵屏模块4

37款传感器与执行器的提法,在网络上广泛流传,其实Arduino能够兼容的传感器模块肯定是不止这37种的。鉴于本人手头积累了一些传感器和执行器模块,依照实践出真知(一定要动手做)的理念,以学习和交流为目的&am…

vba案例1:合并工作簿,工作表

一:放文件 我应该有把文件资源放上去,第一次弄,不知道你们那边能不能看到excel的电子档表格,没有看到,教教我怎么放上去哦 二:自定义代码规整(便于查看) 接下来,我们进行代码解释…

数学建模学习(6):数学建模数据预处理专题

1 数据预处理是什么? 在数学建模赛题中,官方给所有参赛选手的数据可能受到主 观或客观条件的影响有一定的问题,如果不进行数据的处理而直 接使用的话可能对最终的结果造成一定的影响,因此为了保证数 据的真实性和建模结果的可靠…

简单理解大模型参数高效微调中的LoRA(Low-Rank Adaptation)

[论文地址] [代码] [ICLR 22] 阅前须知:本博文可能有描述不准确/过度简化/出错的地方,仅供参考。 网络结构 其中,原有模型的参数是直接冻结的,可训练参数只有额外引入的LoRA参数(由nn.Parameter实现)。 模型微调的本质 记网络原…

MySQL数据库——DML基本操作

文章目录 前言插入数据全列插入指定列插入 修改数据删除数据 前言 前面我们学习了MySQL——DDL操作,对数据库和表的结构的操作,那么今天我将为大家分享MySQL——DML操作,对表数据的操作。 MySQL DML操作有以下几种: 插入操作&am…

opencv-28 自适应阈值处理-cv2.adaptiveThreshold()

什么是自适应阈值处理? 对于色彩均衡的图像,直接使用一个阈值就能完成对图像的阈值化处理。但是,有时图像的色彩是不均衡的,此时如果只使用一个阈值,就无法得到清晰有效的阈值分割结果图像。 有一种改进的阈值处理技术&#xff…

【六大锁策略-各种锁的对比-Java中的Synchronized锁和ReentrantLock锁的特点分析-以及加锁的合适时机】

系列文章目录 文章目录 系列文章目录前言一、六大"有锁策略"1. 乐观锁——悲观锁2. 轻量级锁——重量级锁3. 自旋锁——挂起等待锁4. 互斥锁——读写锁5. 可重入锁——不可重入锁6. 公平锁——非公平锁 二、Synchronized——ReentrantLockSynchronized的特点&#xf…

掌握Python的X篇_13_Python条件语句实例:判断闰年、成绩评定

前面学习了条件语句以及调试的基本技巧,本篇介绍两个与条件语句有关的实例,对前面的知识又深刻认识。 文章目录 1. 判断闰年1.1 版本11.2 版本21.3 一行代码太长的处理方法 2. 根据成绩评级 1. 判断闰年 用户输入年份,判断该年份是否为闰年…

相对位置编码和绝对位置编码

位置编码的区别: 相对位置编码和绝对位置编码是两种不同的位置编码方法。 绝对位置编码是一种基于位置嵌入的方法,其中每个位置都被分配了一个唯一的位置向量。这些向量是固定的,与输入序列的内容无关。这种编码方式对于处理较短的序列效果…

【图论】树上差分(点差分)

一.题目 输入样例: 5 10 3 4 1 5 4 2 5 4 5 4 5 4 3 5 4 3 4 3 1 3 3 5 5 4 1 5 3 4 输出样例:9 二 .分析 我们可以先建一棵树 但我们发现,这样会超时。 所以,我们想到树上差分 三.代码 /* 5 10 3 4 1 5 4 2 5 4 5 4 5 4 3 5 …

基金经理二季度AI概念股操作分化

公募基金二季度仍在加仓AI板块,但不同于一季度全线加仓题材各环节,二季度对AI产业链的操作出现分化。 资金更加聚拢在业绩率先兑现的上游算力板块。其中光模块、服务器是加仓最为显著的两个领域;对于部分业绩短期兑现前景不明的AI板块&#…

机器学习 day31(baseline、学习曲线)

语音识别的Jtrain、Jcv和人工误差 对于逻辑回归问题,Jtrain和Jcv可以用分类错误的比例,这一方式来代替单单只看Jtrain,不好区分是否高偏差。可以再计算人类识别误差,即人工误差,作为基准线来进行比较Jtrain与baselin…

论文分享:PowerTCP: Pushing the Performance Limits of Datacenter Networks

1 原论文的题目(中英文)、题目中包含了哪些关键词?这些关键词的相关知识分别是什么? 题目:PowerTCP: Pushing the Performance Limits of Datacenter Networks PowerTCP:逼近数据中心的网络性能极限 2 论…

银河麒麟安装mysql数据库(mariadb)-银河麒麟安装JDK-银河麒麟安装nginx(附安装包)

银河麒麟离线全套安装教程(手把手教程) 1.银河麒麟服务器系统安装mysql数据库(mariadb) 2.银河麒麟桌面系统安装mysql数据库(mariadb) 3.银河麒麟服务器系统安装JDK 4.银河麒麟桌面系统安装JDK 5.银河麒麟…

【Linux后端服务器开发】MAC地址与其他重要协议

目录 一、以太网 二、MAC地址 三、MTU 四、ARP协议 五、DNS系统 六、ICMP协议 七、NAT技术 八、代理服务器 一、以太网 “以太网”不是一种具体的网路,而是一种技术标准:既包含了数据链路层的内容,也包含了一些物理层的内容&#xf…

Linuxcnc-ethercat从入门到放弃(1)、环境搭建

项目开源网站 LinuxCNChttps://www.linuxcnc.org/当前release版本2.8.4 Downloads (linuxcnc.org)https://www.linuxcnc.org/downloads/可以直接下载安装好linuxcnc的实时debian系统,直接刻盘安装就可以了 安装IgH主站,网上有很多教程可供参考 git clo…

【Rust】枚举类型创建单链表以及常见的链表操作方法

目录 单链表 用枚举表达链表 枚举enum Box容器 创建节点 1. 创建并打印 2. match 匹配 3. 节点初始化 4.节点嵌套 追加节点 1. 尾插法 2. 链表追加方法 3. 头插法 4. 改写成单链表方法 遍历链表 1. 递归法 2. 递推法 3. 改写成单链表方法 自定义Display tr…