1 什么是hash冲突

我们知道HashMap底层是由数组+链表/红黑树构成的,当我们通过put(key, value)向hashmap中添加元素时,需要通过散列函数确定元素究竟应该放置在数组中的哪个位置,当不同的元素被放置在了数据的同一个位置时,后放入的元素会以链表的形式,插在前一个元素的尾部,这个时候我们称发生了hash冲突。

2 如何解决hash冲突

事实上,想让hash冲突完全不发生,是不太可能的,我们能做的只是尽可能的降低hash冲突发生的概率:下面介绍在HashMap中是如何应对hash冲突的?

当我们向hashmap中put元素(key, value)时,最终会执行putVal()方法,而在putVal()方法中,又执行了hash(key)这个操作,并将执行结果作为参数传递给了putVal方法。那么我们先来看hash(key)方法干了什么。

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);
}

(h = key.hashCode()) ^ (h >>> 16)执行了三步操作 :我们一步一步来分析:

第1步:h = key.hashCode()

这一步会根据key值计算出一个int类型的h值也就是hashcode值,例如

"helloWorld".hashCode() --> -1554135584
"123456".hashCode() --> 1450575459
"我爱java".hashCode() --> -1588929438

至于hashCode()是如何根据key计算出hashcode值的,要分几种情况进行分析:

  1. 如果我们使用的自己创建的对象,在我们没有重写hashCode()方法的情况下,会调用Object类的hashCode()方法,而此时返回就是对象的内存地址值,所以如果对象不同,那么通过hashcode()计算出的hashcode就是不同的。

  2. 如果是使用java中定义的引用类型例如String,Integer等作为key,这些类一般都会重写hashCode()方法,有兴趣可以翻看一下对应的源码。简单来说,Integer类的hashCode()返回的就是Integer值,而String类型的hashCode()方法稍稍复杂一点,这里不做展开。总的来说,hashCode()方法的作用就是要根据不同的key得到不同的hashCode值。

第2步:h >>> 16

这一步将第1步计算出的h值无符号右移16位。

为什么要右移16位,当然是位了第三步的操作。

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

将hashcode值的高低16位进行异或操作(同0得0、同1得0、不同得1)得到hash值,举例说明:

  • 假设h值为:1290846991

  • 它的二进制数为:01001100 11110000 11000011 00001111

  • 右移十六位之后:00000000 00000000 01001100 11110000

  • 进行异或操作后:01001100 11110000 10001100 11110000

  • 最终得到的hash值:1290833136

那么问题来了: 明明通过第一步得到的hashcode值就可以作为hash返回,为什么还要要进行第二步和第三步的操作呢?答案是为了减少hash冲突!

元素在数组中存放的位置是由下面这行代码决定的:

// 将(数组的长度-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的位置

node1和node2存储的位置如下图所示:

执行1、4两个步骤:

1.h = key.hashCode()

  • 计算的结果同样为:h = 3654061296

  • 对应的二进制数为:    01101100 11100110 10001100 11110000

4.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

4.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的位置

node1和node2存储的位置如下图所示:

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

当数组长度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 & ni = hash & (n-1)有什么异同。

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

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

HashMap解决hash冲突相关推荐

  1. HashMap解决hash冲突的方法

    HashMap解决hash冲突的方法 博客分类: jvm虚拟机 在Java编程语言中,最基本的结构就是两种,一种是数组,一种是模拟指针(引用),所有的数据结构都可以用这两个基本结构构造,HashMap ...

  2. Hashmap解决hash冲突为什么使用红黑树

    首先,在解决这个问题之前我们要先了解hash冲突是什么. hash冲突 hashmap在添加新的键值对的时候,会根据key值通过一个函数计算出一个结果作为地址值,根据这个地址值将其键值对插入到对应的位 ...

  3. HashMap解决Hash冲突为什么要使用红黑树

    1.HashMap的数据结构 HashMap是由数组与链表来实现的,同时,也具备了数组和链表的所以特点 2.什么是hash冲突 hash冲突:就是根据key即经过一个哈希变换得到的结果的作为地址去存放 ...

  4. 1.Hashmap 解决hash冲突为什么使用红黑树

    1.  hash冲突指的是在向Hash表中存数据时,首先要用Hash函数计算出该数据要存放的地址.但是在这个地址中已经有值存在,所以这个时候就发生了Hash冲突.也就是说:值不同的元素可能会映象到哈希 ...

  5. mysql映射成hashmap_大厂面试必问!HashMap 怎样解决hash冲突?

    HashMap冲突解决方法比较考验一个开发者解决问题的能力. 下文给出HashMap冲突的解决方法以及原理分析,无论是在面试问答或者实际使用中,应该都会有所帮助. 在Java编程语言中,最基本的结构就 ...

  6. HashMap 如何解决 hash 冲突

    HashMap 底层采用数组的结构来存储数据元素,数组的默认长度是 16,通过 put 方法添加数据的时候,HashMap 根据 key 的 hash 值进行取模运算,最终保存到数组的指定位置 这种设 ...

  7. HashMap 怎样解决hash冲突?

    HashMap冲突解决方法比较考验一个开发者解决问题的能力. 下文给出HashMap冲突的解决方法以及原理分析,无论是在面试问答或者实际使用中,应该都会有所帮助. 在Java编程语言中,最基本的结构就 ...

  8. HashMap 中 hash 冲突的解决方法及原理分析

    我们最先衰老的不是容貌,而是不顾一切的闯劲.有时候,要敢于背上超出自己预料的包袱,真的努力后,你会发现自己要比想象的优秀很多. HashMap冲突的解决方法比较考验一个开发者解决问题的能力. 在Jav ...

  9. 【Java面试小短文】HashMap是如何解决Hash冲突的?

    欢迎关注Java面试系列,不定期更新面试小短文.欢迎一键三连! 文章目录 什么是Hash算法? 什么是Hash表? HashMap是如何解决Hash冲突的? 什么是Hash算法?   Hash 算法, ...

  10. java开放地址法和链地址法解决hash冲突

    hashMap对各位小伙们来说,没有不知道的了,使用过的人想必或多或少的都了解一点hashMap的底层实现原理,总结来说就是,数组+链表,至于源码的实现,大家可参看源码,今天想说的是hashMap是怎 ...

最新文章

  1. 有监督、无监督与半监督学习【总结】
  2. NIPS 2018 论文解读集锦(11月28日更新)
  3. 做一个java项目要经过那些正规的步骤
  4. 最大流,最小费用最大流:解析 + 各种板子
  5. 解决ERROR 1396 (HY000): Operation ALTER USER failed for root@localhost
  6. 电影《长津湖》上映7天票房破26亿元
  7. java se 7u67_Java SE 7u72和Java SE 7u71有什么不同
  8. 计算机公开课打字游戏,H5公开课:古堡密卷--打字通关游戏设计及开发
  9. java 代码统计工具_java代码行数统计工具
  10. multisim连接MySQL_Multisim14使用multisim12元件库的方法
  11. LaTex中让页码从正文开始编号
  12. T32使用-----抓取rpm dump
  13. 迅雷欲缔造互联网“视频梦工厂”
  14. 生成对抗网络(GAN)论文原文详解
  15. 卸载xampp并重装mysql
  16. 细读论文基本概念了解
  17. SAP_常用BADI清单
  18. opencv中直方图均衡函数cv::equalizeHist()的使用!!严格用程序进行了验证!
  19. 音视频学习之-YUV裸数据
  20. 【HAL库】STM32F407ZGT6实现串口中断发送和接收

热门文章

  1. linux设置ipsan_Linux挂载IPSAN和FCSAN操作
  2. 树莓派网易云音乐播放器
  3. Vercel部署网易云音乐api
  4. snmp使用默认团体名_CactiEZ 中文版snmp默认团体名
  5. 100名网工备考IE,最终能通过的,到底有几个?
  6. 痛惜!浙大一学科带头人病逝,年仅47岁
  7. 通达OA 商务平台OA2017新版本简易评测(图文)
  8. STM32F107+LWIP+FreeRTOS
  9. SPSS个案处理插件v1.0 用SPSS构造Bootstrap样本
  10. python gui与pyside