JDK 1.8 中 HashMap 的 hash 算法和寻址算法

HashMap 源码

hash() 方法

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

h = key.hashCode() 表示 h 是 key 对象的 hashCode 返回值;

h >>> 16 是 h 右移 16 位,因为 int 是 4 字节,32 位,所以右移 16 位后变成:左边 16 个 0 + 右边原 h 的高 16 位;

最后把这两个进行异或返回。

异或:二进制位运算。如果一样返回 0,不一样则返回 1。

例:两个二进制 110 和 100 进行异或

​ 110

^ 100

结果= 010

putVal() 中寻址部分

tab[i = (n - 1) & hash]

tab 就是 HashMap 里的 table 数组 Node<K,V>[] table

n 是这个数组的长度 length;

hash 就是上面 hash() 方法返回的值;

为什么不直接用 hashCode() % length ?

看完源码会有疑问,为什么不直接用 key 对象的 hashCode 对哈希表长度取模?

寻址为什么不用取模?

对于上面寻址算法,由于计算机对比取模,与运算会更快。所以为了效率,HashMap 中规定了哈希表长度为 2 的 k 次方,而 2^k-1 转为二进制就是 k 个连续的 1,那么 hash & (k 个连续的 1) 返回的就是 hash 的低 k 个位,该计算结果范围刚好就是 0 到 2^k-1,即 0 到 length - 1,跟取模结果一样。

也就是说,哈希表长度 length 为 2 的整次幂时, hash & (length - 1) 的计算结果跟 hash % length 一样,而且效率还更好。

为什么不直接用 hashCode() 而是用它的高 16 位进行异或计算新 hash 值?

int 类型占 32 位,可以表示 2^32 种数(范围:-2^31 到 2^31-1),而哈希表长度一般不大,在 HashMap 中哈希表的初始化长度是 16(HashMap 中的 DEFAULT_INITIAL_CAPACITY),如果直接用 hashCode 来寻址,那么相当于只有低 4 位有效,其他高位不会有影响。这样假如几个 hashCode 分别是 210、220、2^30,那么寻址结果 index 就会一样而发生冲突,所以哈希表就不均匀分布了。

为了减少这种冲突,HashMap 中让 hashCode 的高位也参与了寻址计算(进行扰动),即把 hashCode 高 16 位与 hashCode 进行异或算出 hash,然后根据 hash 来做寻址。

JDK 源码中 HashMap 的 hash 方法原理是什么?

JDK1.8中HashMap的hash算法和寻址算法的更多相关文章

  1. JDK1.8中HashMap实现

    JDK1.8中的HashMap实现跟JDK1.7中的实现有很大差别.下面分析JDK1.8中的实现,主要看put和get方法. 构造方法的时候并没有初始化,而是在第一次put的时候初始化 putVal方 ...

  2. JDK1.7中HashMap死环问题及JDK1.8中对HashMap的优化源码详解

    一.JDK1.7中HashMap扩容死锁问题 我们首先来看一下JDK1.7中put方法的源码 我们打开addEntry方法如下,它会判断数组当前容量是否已经超过的阈值,例如假设当前的数组容量是16,加 ...

  3. 记一次诡异的bug调试——————关于JDK1.7和JDK1.8中HashSet的hash(key)算法的区别

    现象: 测试提了一个bug,我完全复现不了,但是最吊诡的是在其他人的机器上都可以复现.起初以为是SVN合并后出现的冲突,后来经过对比法排查: step 1: 我本地开两个jetty,一个跑合并之前的版 ...

  4. JDK1.7中HashMap底层实现原理

    一.数据结构 HashMap中的数据结构是数组+单链表的组合,以键值对(key-value)的形式存储元素的,通过put()和get()方法储存和获取对象. (方块表示Entry对象,横排表示数组ta ...

  5. 关于JDK1.7+中HashMap对红黑树场景的思考

    背景 在1.7之前的版本,当数组元素较多(几百.几千,或者更多)的时候,在这种前提扩容,涉及全量元素的遍历和坐标的重新定位,这个耗时会比较长.这是之前存在的一个弊端吧.那么引入红黑树之后就解决了问题, ...

  6. jdk1.8中hashmap的扩容resize

    当hashmap第一次插入元素.元素个数达到容量阀值threshold时,都会扩容resize(),源码: (假设hashmap扩容前的node数组为旧横向node数组,扩容后的node数组为新横向n ...

  7. Java中HashMap的hash分布策略的简单解释

    趴源码是看到一段不可思议的代码,网上的解释似乎不大令人满意,因此稍微花点时间解读了一下,如有错误请指正 HashMap的桶是这样搞的 // 片段1 static final int hash(Obje ...

  8. jdk1.7中hashmap扩容时不会产生死循环

    在扩容时 transfer( ) 方法中 newTable 新数组 局部变量 table 旧数组 全局变量 当第一个链表进行while循环时 执行到 e.next = newTable[i]; 时 n ...

  9. hashMap在jdk1.7与jdk1.8中的原理及不同

    在分析jdk1.7中HashMap的hash冲突时,不知大家是否有个疑问就是万一发生碰撞的节点非常多怎么版?如果说成百上千个节点在hash时发生碰撞,存储一个链表中,那么如果要查找其中一个节点,那就不 ...

随机推荐

  1. Linux使用pyinstaller 编译py成可执行程序

    0x00 pyinstall 安装 命令行pip install pyinstaller安装 0x01 编译时使用命令pyinstaller -F -w  /root/pyfile/图片GPS位置获取 ...

  2. python进阶之内置方法

    python进阶之内置方法 字符串类型的内置方法 常用操作与方法: 按索引取值 str[index] 切片 ste[start:stop:step] 长度 len(str) 成员运算in和not in ...

  3. 大前端时代搞定PC/Mac端开发,我有绝招

    如果你是一位前端开发工程师,对"跨平台"一词应该不会感到陌生.像常见的前端框架:比如React.Vue.Angular,它们可以做网页端,也可以做移动端,但很少能做到跨PC.Mac ...

  4. scrapy 基础组件专题(四):信号运用

    一.scrapy信号使用的简单实例 import scrapy from scrapy import signals from ccidcom.items import CcidcomItem cla ...

  5. 数据可视化之DAX篇(六) 利用ISINSCOPE函数,轻松按层级计算占比

    https://zhuanlan.zhihu.com/p/70590683 关于占比,之前有篇文章(利用ALL和ALLSELECTED灵活计算占比)详细介绍了各种情况下占比的度量值. 经星友咨询,还有 ...

  6. Thymeleaf模板引擎学习

    开发传统Java WEB项目时,我们可以使用JSP页面模板语言,但是在SpringBoot中已经不推荐使用JSP页面进行页面渲染了.从而Thymeleaf提供了一个用于整合Spring MVC的可选模 ...

  7. bzoj3374[Usaco2004 Mar]Special Serial Numbers 特殊编号*

    bzoj3374[Usaco2004 Mar]Special Serial Numbers 特殊编号 题意: 求比一个数大的最小的一半以上的数位相同的数.数位数≤100. 题解: 模拟题.从低位枚举到 ...

  8. JavaScript 基础 学习 (一)

    JavaScript 基础 学习 获取页面中的元素的方法 作用:通过各种方式获取页面中的元素 ​ 比如:id,类名,标签名,选择器 的方式来获取元素 ​ 伪数组: ​ 长的和数组差不多,也是按照索引排 ...

  9. 用Vue实现一个简单的图片轮播

    本文已收录至https://github.com/likekk/studyBlog欢迎大家star,共同学习,共同进步.如果文章有错误的地方,欢迎大家指出.后期将在将GitHub上规划前端学习的路线和 ...

  10. NCRE-Python考点

    NCRE-Python考点 作者:封亚飞本文不含 文件处理.面向对象程序设计.公共基础.计算生态希望各位可以批评指正Qq 64761294 由于图片上传不方便,需要真题的朋友可以加我的qq找我要pdf ...