Java连载152-HashMap中的hash函数有什么用

一、取模运算和取余运算

  • 取余运算,这个很好理解,我们经过多年的数学学习也知道,就是求余数,一个整数和另一个整数相除,得到它们的余数,就是我们说的取余
  • 取模运算,通俗的来讲大多运算在计算机领域,取模运算其实就是两个二进制数字之间做与运算,它们最后得到的数字就是取模
  • 我们举个简单的例子,有一个二进制数字0000 0001 1001 1101,1111 0101 1010 0011,这个两个数字做与运算,它们相同位置的数字,如果有一个数字出现1,那么计算后的数字的那个位置就是1,这两个数字与运算后的值为1111 0101 1011 1111这就是取模运算

二、hash函数在HashMap的源码

&#xA0;&#xA0;&#xA0;&#xA0;<span class="hljs-function"><span class="hljs-keyword">static</span>&#xA0;<span class="hljs-keyword">final</span>&#xA0;<span class="hljs-keyword">int</span>&#xA0;<span class="hljs-title">hash</span><span class="hljs-params">(Object&#xA0;key)</span>&#xA0;</span>{<br>&#xA0;&#xA0;&#xA0;&#xA0;&#xA0;&#xA0;&#xA0;&#xA0;<span class="hljs-keyword">int</span>&#xA0;h;<br>&#xA0;&#xA0;&#xA0;&#xA0;&#xA0;&#xA0;&#xA0;&#xA0;<span class="hljs-keyword">return</span>&#xA0;(key&#xA0;==&#xA0;<span class="hljs-keyword">null</span>)&#xA0;?&#xA0;<span class="hljs-number">0</span>&#xA0;:&#xA0;(h&#xA0;=&#xA0;key.hashCode())&#xA0;^&#xA0;(h&#xA0;>>>&#xA0;<span class="hljs-number">16</span>);<br>&#xA0;&#xA0;&#xA0;&#xA0;}
  • 我们乍一看这个东西很懵,到底有什么用,讲这个源码之前,我们得先知道hashCode()函数是干嘛的?我们先看源码
&#xA0;&#xA0;&#xA0;&#xA0;<br>&#xA0;&#xA0;&#xA0;&#xA0;<span class="hljs-function"><span class="hljs-keyword">public</span>&#xA0;<span class="hljs-keyword">native</span>&#xA0;<span class="hljs-keyword">int</span>&#xA0;<span class="hljs-title">hashCode</span><span class="hljs-params">()</span></span>;
  • 这个就是hashCode函数的实现,从native关键字可以看出这是一个由底层C代码实现的函数,因此我们不必知道它是怎么实现,我们只需要知道,它就是返回一个int类型的数字,这个数字就相当于对象的身份证,指纹,因此可以利用这个”身份证”来进行归类,我们知道int值域在-2147483648 到 2147483648,这个范围有40多亿,因此想要相同也不容易,但是我们的HashMap底层数据结构是一个数组啊,用于存储这些节点,初始大小为16,因此按照我们常规做法就是,用这个”身份证”和数组大小(也就是16)做取余运算,这样我们就是把一个很大的数字映射成一个16以内的整数。然后我们就可以把这个节点存储到这个数组的某一个位置了。

三、hashMap中映射的解决方案

  • 这个解决方案其实很好,但是我们还没有其他的解决方案呢?
  • 其实是有的,在hashmap中的做映射,使用是取模运算而不是取余运算,这是为啥?
  • 因为取余运算的效率远高于取余运算,我们知道任何数据存储在计算机内部都是以二进制形式存储的,因为两个int整数进行取模运算的时候,做几次比较立即就得出了,但是取余就要运算很长
  • hashMap内部使用取模,比如我们一个对象的”身份证”是
  • 1010 0111 0100 1010 1111 1001 0010 1010
  • 我们数组大小是16,16的二进制是0000 0000 0000 0000 0000 0000 0001 0000
  • 我们减一,那就是 0000 0000 0000 0000 0000 0000 0000 1111,用这个数字和上面那个”身份证”取模得到0000 * 7个,最后四位就是1010,多好啊!把一个数字映射成了一个16以内的数字,我们看看源码是不是这个方法
&#xA0;&#xA0;&#xA0;&#xA0;<span class="hljs-function"><span class="hljs-keyword">final</span>&#xA0;V&#xA0;<span class="hljs-title">putVal</span><span class="hljs-params">(<span class="hljs-keyword">int</span>&#xA0;hash,&#xA0;K&#xA0;key,&#xA0;V&#xA0;value,&#xA0;<span class="hljs-keyword">boolean</span>&#xA0;onlyIfAbsent,<br>&#xA0;&#xA0;&#xA0;&#xA0;&#xA0;&#xA0;&#xA0;&#xA0;&#xA0;&#xA0;&#xA0;&#xA0;&#xA0;&#xA0;&#xA0;&#xA0;&#xA0;&#xA0;&#xA0;<span class="hljs-keyword">boolean</span>&#xA0;evict)</span>&#xA0;</span>{<br>&#xA0;&#xA0;&#xA0;&#xA0;&#xA0;&#xA0;&#xA0;&#xA0;Node<k,v>[]&#xA0;tab;&#xA0;Node<k,v>&#xA0;p;&#xA0;<span class="hljs-keyword">int</span>&#xA0;n,&#xA0;i;<br>&#xA0;&#xA0;&#xA0;&#xA0;&#xA0;&#xA0;&#xA0;&#xA0;<span class="hljs-keyword">if</span>&#xA0;((tab&#xA0;=&#xA0;table)&#xA0;==&#xA0;<span class="hljs-keyword">null</span>&#xA0;||&#xA0;(n&#xA0;=&#xA0;tab.length)&#xA0;==&#xA0;<span class="hljs-number">0</span>)<br>&#xA0;&#xA0;&#xA0;&#xA0;&#xA0;&#xA0;&#xA0;&#xA0;&#xA0;&#xA0;&#xA0;&#xA0;n&#xA0;=&#xA0;(tab&#xA0;=&#xA0;resize()).length;<br>&#xA0;&#xA0;&#xA0;&#xA0;&#xA0;&#xA0;&#xA0;&#xA0;<span class="hljs-keyword">if</span>&#xA0;((p&#xA0;=&#xA0;tab[i&#xA0;=&#xA0;(n&#xA0;-&#xA0;<span class="hljs-number">1</span>)&#xA0;&&#xA0;hash])&#xA0;==&#xA0;<span class="hljs-keyword">null</span>)<br>&#xA0;&#xA0;&#xA0;&#xA0;&#xA0;&#xA0;&#xA0;&#xA0;&#xA0;&#xA0;&#xA0;&#xA0;tab[i]&#xA0;=&#xA0;newNode(hash,&#xA0;key,&#xA0;value,&#xA0;<span class="hljs-keyword">null</span>);<br><br></k,v></k,v>
  • 可以看见正好n-1就是16-1=15这样2进制最后4位都是1,就是模取出来了
  • 这也正好解释了为什么扩容都是*2了,因为-1之后就是后面几位数字都是1了,便于好取模,求出映射
  • 回到我们刚才说的hash函数有什么用
  • 我们看这里h&(h>>16),其实就是身份证和身份证往右移动16位之后取模,我们知道一个int值类型就是一个32位的二进制,这样其实就是把前16和后16位进行取模,这样就把一个增大了随机性,因为你看原来的方法只取了后面4位,前面的28位都没啥用,用这个方法之后,再取后4位就是把前面的16位也拿来了
  • 总结 :hash函数就是为了是对象的hash值增加随机性,它集合前16位和后16的特征进行计算,这样再被取模之后,会让对象在HashMap中分布更均匀。

四、源码:

Original: https://www.cnblogs.com/ruigege0000/p/15700598.html
Author: 心悦君兮君不知-睿
Title: Java连载152-HashMap中的hash函数有什么用

原创文章受到原创版权保护。转载请注明出处:https://www.johngo689.com/607928/

转载文章受原作者版权保护。转载请注明原作者出处!

(0)

大家都在看

  • Java缓冲流、转换流、节点流、处理流

    一、BufferrReader(带有缓冲区的字符输入流) 使用这个流的时候不需要自定义char[ ] 数组,或者说不需要自定义char[ ] 数组。自带缓冲 1.构造方法 方法名 …

    Java 2023年6月9日
    086
  • 好玩Python——PIL项目实训(二)

    1 # -*- coding: utf-8 -*- 2 """ 3 Created on Sun Apr 12 22:03:26 2020 4 5 @…

    Java 2023年6月6日
    0112
  • 硬核剖析Redis单线程为什么那么快?

    Redis目前是使用率最高的内存库数据库,是企业应用开发的必备,它极高的性能和丰富的数据结构为我们的开发提供了极大的便利。它每秒可以承受10W+的QPS,但却是单线程的处理模型,为…

    Java 2023年6月16日
    099
  • 七牛云 融合CDN测试域名 -> 融合CDN加速域名

    七牛云 融合CDN测试域名 -> 融合CDN加速域名 本篇主要讲解 如何将七牛云融合CDN测试域名 切换到自定义的加速域名上去, 为什么会写这篇是因为我收到了一封 【七牛云】…

    Java 2023年6月9日
    085
  • JSP学习笔记

    jsp的全称是java server pages。其主要作用是代替Servlet程序回传html页面的数据。 JSP的本质 JSP页面本质是一个Servlet程序。当我们第一次访问…

    Java 2023年6月8日
    075
  • spring-mvc快速入门

    Spring-mvc    1.入门    1.在pom.xml导入坐标                     org.springframework            sp…

    Java 2023年6月9日
    077
  • 高并发问题中 缓存 降级 限流 而限流是怎么实现的?

    在开发高并发系统时,有三把利器用来保护系统:缓存、降级和限流。那么何为限流呢?顾名思义,限流就是限制流量,就像你宽带包了1个G的流量,用完了就没了。通过限流,我们可以很好地控制系统…

    Java 2023年6月6日
    082
  • 使用ANSI改变终端输出样式

    默认情况下程序输出到终端的字符样式为白字黑背景,样式、字体比较单一。如想改变程序输出到终端字符的样式等可使用ANSI转移码使其输出具有不同样式; ANSI转义序 ANSI转义序列包…

    Java 2023年6月16日
    073
  • 死锁详解

    死锁的定义 所谓死锁,是指多个进程在运行过程中因争夺资源而造成的一种僵局,每个进程持有某种资源而又都等待着别的进程释放它或它们现在保持着的资源,当进程处于这种僵持状态时,若无外力作…

    Java 2023年6月15日
    0102
  • Java maven反应堆构建学习实践

    实践环境 Apache Maven 3.0.5 (Red Hat 3.0.5-17) 应用示例 maven示例项目组织结构如下 maven-study &#x2502; p…

    Java 2023年5月29日
    080
  • 如何给注册中心锦上添花?

    hello,大家好,我是小楼。 在上一篇文章《如何组装一个注册中心》中,我们看到了如何利用一些现有的技术方案来组装出一个生产可用的注册中心最小集。 有的同学看完表示学到了,也有同学…

    Java 2023年6月6日
    087
  • Spring Boot 使用 EnvironmentPostProcessor 接口实现加载外部配置文件

    从Spring Boot 1.3开始,我们可以在应用程序上下文刷新之前使用 EnvironmentPostProcessor来自定义应用程序的 Environment。 Envir…

    Java 2023年5月30日
    092
  • tar、gzip、zip、jar是什么,怎么查看?

    原创:扣钉日记(微信公众号ID:codelogs),欢迎分享,转载请保留出处。 如果你是后端程序员,我想你一定见过 *.tar.gz、 *.zip、 *.jar后缀的文件吧,这些都…

    Java 2023年6月7日
    097
  • 逻辑存储单元

    dbspace dbspace&#x662F;&#x4E00;&#x4E2A;&#x6216;&#x8005;&#x591A;&am…

    Java 2023年6月9日
    099
  • BAT 基础语法

    命令 //功能 echo //标准输出命令 在CMD窗口中 显示echo 后的内容 @ //关闭当前行的 回显 回显:源代码在 CMD 窗口中再次显示 pasue // 暂停程序 …

    Java 2023年6月8日
    092
  • JDK自带线程池学习

    JDK自带线程池 线程池的状态 线程有如下状态 RUNNING状态:Accept new tasks and process queued tasks SHUTDOWN状态:Don…

    Java 2023年6月16日
    083
亲爱的 Coder【最近整理,可免费获取】👉 最新必读书单  | 👏 面试题下载  | 🌎 免费的AI知识星球