public V put(K key, V value) {return putVal(hash(key), key, value, false, true);
}static final int hash(Object key) {int h;// 判断key是否为null, 如果为null,则直接返回0;// 如果不为null,则返回(h = key.hashCode()) ^ (h >>> 16)的执行结果return (key == null) ? 0 : (h = key.hashCode()) ^ (h >>> 16);
}

我们一步一步来分析

  • 第1步:h = key.hashCode()
"helloWorld".hashCode() --> -1554135584
"123456".hashCode() --> 1450575459
"我爱java".hashCode() --> -1588929438
  • 第2步:h >>> 16
    无符号右移(>>>):
    对于正数的带符号右移,不论正数还是负数,移位过程中高位均补零。

  • 第3步:h ^ (h >>> 16)

假设h值为:1290846991
它的二进制数为:01001100 11110000 11000011 00001111
右移十六位之后:00000000 00000000 01001100 11110000
进行异或操作后:01001100 11110000 10001100 11110000
最终得到的hash值:1290833136
  • 第四步:计算元素在数组中存放的位置
    由下面这行代码决定的:
// 将(数组的长度-1)和hash值进行按位与操作:
i = (n - 1) & hash  // i为数组对应位置的索引  n为当前数组的大小

我们将上面这步操作作为第4步操作,来对比一下执行1、2、3、4四个步骤和只执行第1、4两个步骤所产生的不同效果。

我们向hashmap中put两个元素node1(key1, value1)、node2(key2, value2),hashmap的数组长度n=16。

执行1、2、3、4 四个步骤:

  1. h = key.hashCode()
    假设计算的结果为:h = 3654061296
    对应的二进制数为:    01101100 11100110 10001100 11110000
  2. h >>> 16
    h无符号右移16位得到: 00000000 00000000 01101100 11100110
  3. hash = h ^ (h >>> 16)
    异或操作后得到hash:  01101100 11110000 11100000 00000110
  4. i = (n-1) & hash
    n-1=15 对应二进制数 :    00000000 00000000 00000000 00001111
    hash :   01101100 11110000 11100000 00000110
    hash & 15 :    00000000 00000000 00000000 00000110
    转化为10进制 : &ensp 5
    最终得到i的值为5,也就是说node1存放在数组索引为5的位置。

同理我们对(key2, value2) 进行上述同样的操作过程:

  1. h = key.hashCode()
    假设计算的结果为:h = 3652881648
    对应的二进制数为:    01101100 11011101 10001100 11110000
  2. h >>> 16
    h无符号右移16位得到: 00000000 00000000 01101100 11011101
  3. hash = h ^ (h >>> 16)
    异或操作后得到hash:  01101100 11110000 11100000 00101101
  4. i = (n-1) & hash
    n-1=15 对应二进制数 :    00000000 00000000 00000000 00001111
    hash :   01101100 11110000 11100000 00101101
    hash & 15 :   00000000 00000000 00000000 00001101
    转化为10进制 : &ensp 13
    最终得到i的值为13,也就是说node2存放在数组索引为13的位置

执行1、4两个步骤:

  1. h = key.hashCode()
    计算的结果同样为:h = 3654061296
    对应的二进制数为:    01101100 11100110 10001100 11110000
  2. i = (n-1) & hash
    n-1=15 对应二进制数 :    00000000 00000000 00000000 00001111
    hash(h) :   01101100 11100110 10001100 11110000
    hash & 15 :   00000000 00000000 00000000 00000000
    转化为10进制 :   0
    最终得到i的值为0,也就是说node1存放在数组索引为0的位置

同理我们对(key2, value2) 进行上述同样的操作过程:

  1. h = key.hashCode()
    计算的结果同样为:h = 3652881648
    对应的二进制数为:    01101100 11011101 10001100 11110000
  2. i = (n-1) & hash
    n-1=15 对应二进制数 :    00000000 00000000 00000000 00001111
    hash(h) :   01101100 11110000 11100000 11110000
    hash & 15 :   00000000 00000000 00000000 00000000
    转化为10进制 :   0
    最终得到i的值为0,也就是说node2同样存放在数组索引为0的位置

相信大家已经看出区别了:

​ ​ 当数组长度n较小时,n-1的二进制数高16位全部位0,这个时候如果直接和h值进行&(按位与)操作,那么只能利用到h值的低16位数据,这个时候会大大增加hash冲突发生的可能性,因为不同的h值转化为2进制后低16位是有可能相同的,如上面所举例子中:key1.hashCode() 和key2.hashCode() 得到的h值不同,一个h1 = 3654061296 ,另一个h2 = 3652881648,但是不幸的是这h1、h2两个数转化为2进制后低16位是完全相同的,所以h1 & (n-1)和 h2 & (n-1) 会计算出相同的结果,这也导致了node1和node2 存储在了数组索引相同的位置,发生了hash冲突。

​ 当我们使用进行 h ^ (h >>> 16) 操作时,会将h的高16位数据和低16位数据进行异或操作,最终得出的hash值的高16位保留了h值的高16位数据,而hash值的低16数据则是h值的高低16位数据共同作用的结果。所以即使h1和h2的低16位相同,最终计算出的hash值低16位也大概率是不同的,降低了hash冲突发生的概率。

ps:这里面还有一个值的注意的点: 为什么是(n-1)?

我们知道n是hashmap中数组的长度,那么为要进行n-1的操作?答案同样是为了降低hash冲突发生的概率!

要理解这一点,我们首先要知道HashMap规定了数组的长度n必须为2的整数次幂,至于为什么是2的整数次幂,会在HashMap的扩容方法resize()里详细讲。

既然n为2的整数次幂,那么n一定是一个偶数。那么我们来比较i = hash & n和 i = hash & (n-1)有什么异同。

n为偶数,那么n转化为2进制后最低位一定为0,与hash进行按位与操作后最低位仍一定为0,这就导致i值只能为偶数,这样就浪费了数组中索引为奇数的空间,同时也增加了hash冲突发生的概率。

所以我们要执行n-1,得到一个奇数,这样n-1转化为二进制后低位一定为1,与hash进行按位与操作后最低位即可能位0也可能位1,这就是使得i值即可能为偶数,也可能为奇数,充分利用了数组的空间,降低hash冲突发生的概率。

jdk1.8hashmap为什么对hash进行了一次扰动处理相关推荐

  1. JDK1.8HashMap源码级分析

    任何不谈论JDK版本的HashMap介绍不是小白就是在耍流氓,所以本文是基于JDK1.7版本的HashMap分析,其中涉及到了面试常问的问题以及核心方法的源码分析 JDK1.8HashMap 红黑树前 ...

  2. 基于JDK1.8---HashMap源码分析

    基于JDK1.8-HashMap源码简要分析 HashMap继承关系 HashMap:根据键的 hashCode 值存储数据,大多数情况下可以直接定位到它的值,因而具有很快的访问速度,但遍历顺序却是不 ...

  3. Java源码HashMap、ConcurrentHashMap:JDK1.8HashMap静态常量以及设置的目的,初始容量、最大容量、扩容缩容树化条件

    HashMap核心源码 作为工作中最重要.最常用的容器之一,当然还是要自己动手写一篇 HashMap 的源码解析来加深对其的印象咯,而且它的设计与实现 也有很多值得学习的地方. 以下包含HashMap ...

  4. JDK1.8HashMap会发生死循环吗?

    一.直击面试现场 面试官:你觉得HashMap在多线程并发的情况下会出现死循环吗? 我(暗自窃喜,还好准备了):HashMap在jdk1.8之前,会因为多线程put元素操作共享hashmap会出现,原 ...

  5. 面试必备:HashMap底层数据结构?jdk1.8算法优化,hash冲突,扩容等问题

    面试必备系列不会长篇理论求证,直接上答案,仅供参考,不喜勿喷. 1.能说说HashMap的底层原理吗? HashMap<String,String> map = new HashMap&l ...

  6. hashmap实现原理_Java中HashMap底层实现原理(JDK1.8)源码分析

    在JDK1.6,JDK1.7中,HashMap采用位桶+链表实现,即使用链表处理冲突,同一hash值的链表都存储在一个链表里.但是当位于一个桶中的元素较多,即hash值相等的元素较多时,通过key值依 ...

  7. Java面试绕不开的问题: Java中HashMap底层实现原理(JDK1.8)源码分析

    这几天学习了HashMap的底层实现,但是发现好几个版本的,代码不一,而且看了Android包的HashMap和JDK中的HashMap的也不是一样,原来他们没有指定JDK版本,很多文章都是旧版本JD ...

  8. jdk1.8之HashMap

    转载自:http://blog.csdn.net/qq_27093465/article/details/52207135 摘要 HashMap是Java程序员使用频率最高的用于映射(键值对)处理的数 ...

  9. HashMap JDK 1.8 VS JDK1.7 增加性能

    一.传统 HashMap的缺点 (1)JDK 1.8 以前 HashMap 的实现是 数组+链表,即使哈希函数取得再好,也很难达到元素百分百均匀分布. (2)当 HashMap 中有大量的元素都存放到 ...

最新文章

  1. 第一篇学术演讲准备两个月,去了谷歌却做不了深度学习,听Facebook田渊栋谈人生挑战与选择...
  2. 160个Crackme026之六段式注册码详解
  3. TCP连续ARQ协议和滑动窗口协议
  4. JDK 8的Calendar.Builder
  5. eclipe安装中文语言包 CDT Pydev
  6. Private Bytes,Working Set,Virtual Size的区别
  7. 图像质量评估(4) -- 色差(Chromatic Aberration)
  8. 计算机多媒体技术广泛应用于各个领域,计算机多媒体技术的现状及发展前景
  9. 重复抽样与不重复抽样的抽样平均误差大小?
  10. git clone提示鉴权失败
  11. 怎样把ICO图标改成圆形的?
  12. PuTTY key format too new怎么解决?
  13. 毕业生基本要素之计算机水平,2020上海积分应届毕业生落户政策
  14. 去重数组中相同的字符串
  15. 穆穆推荐-软件销售行业软件公司销售参考操作手册--之1--公司软件产品的定位分类以及目标客户定位-待续....
  16. 关于PC播放器色彩空间转换等一些说明
  17. 友善之臂comtest.c串口编译程序详解 希望对大家有帮助
  18. 赚商联盟:知识付费时代通过购买网课来缓解焦虑,是对还是错?
  19. 百度之星-1001 调查问卷
  20. [溢泽国学讲堂]论气

热门文章

  1. 【原】iOS动态性(三) Method Swizzling以及AOP编程:在运行时进行代码注入
  2. 如何寻找高质量流量日入三百+
  3. 搞不懂SDN和SD-WAN?那是因为你没看这个小故事—Vecloud微云
  4. MPLS基本结构是怎样的?—Vecloud微云
  5. 在CentOS7上编译GreenPlum5.3.0
  6. Oracle存储过程中执行DDL操作
  7. 设置sqlplus环境变量
  8. except but
  9. HashSet/HashMap 存取值的过程
  10. (二)Linux命令使用