Java 学习笔记 HashMap 中的 hash 方法为何要进行异或和位移?
Posted 笑虾
tags:
篇首语:本文由小常识网(cha138.com)小编为大家整理,主要介绍了Java 学习笔记 HashMap 中的 hash 方法为何要进行异或和位移?相关的知识,希望对你有一定的参考价值。
Java 学习笔记 HashMap 中的 hash方法为何要进行异或和位移?
Hash 散列、杂凑,音译哈希(百度百科)
Hash,一般翻译做散列、杂凑,或音译为哈希,是把任意长度的输入通过散列算法变换成固定长度的输出,该输出就是散列值。这种转换是一种压缩映射,也就是,散列值的空间通常远小于输入的空间,不同的输入可能会散列成相同的输出,所以不可能从散列值来确定唯一的输入值。简单的说就是一种将任意长度的消息压缩到某一固定长度的消息摘要的函数。
JDK源码
java.lang.Object#hashCode
public native int hashCode();
返回该对象的哈希码值。支持此方法是为了提高哈希表(例如 java.util.Hashtable 提供的哈希表)的性能。
hashCode 的常规协定是:
- 在 Java 应用程序执行期间,在对同一对象多次调用 hashCode 方法时,必须一致地返回相同的整数,前提是将对象进行 equals 比较时所用的信息没有被修改。从某一应用程序的一次执行到同一应用程序的另一次执行,该整数无需保持一致。
- 如果根据 equals(Object) 方法,两个对象是相等的,那么对这两个对象中的每个对象调用 hashCode 方法都必须生成相同的整数结果。
- 如果根据 equals(java.lang.Object) 方法,两个对象不相等,那么对这两个对象中的任一对象上调用 hashCode 方法不 要求一定生成不同的整数结果。但是,程序员应该意识到,为不相等的对象生成不同整数结果可以提高哈希表的性能。
实际上,由 Object 类定义的 hashCode 方法确实会针对不同的对象返回不同的整数。(这一般是通过将该对象的内部地址转换成一个整数来实现的,但是 JavaTM 编程语言不需要这种实现技巧。)
返回:此对象的一个哈希码值。
通俗的说的就是,调用我完事了,我会给你一个32
位的暗号,底层实现太复杂,年轻人我怕你把握不住。
你只要拿一个32
位的东西(int)接收就行了。左边有没填满的空位都是0
。
java.util.HashMap#hash
/**
* @param key
*/
static final int hash(Object key)
int h;
return (key == null) ? 0 : (h = key.hashCode()) ^ (h >>> 16);
分解演示
int h = 0b11111111111111110000000000000000; // 0b开头表示二进制数
int i = h >>> 16; // 无符号右移16位(包括符号位一起移)
log.info("", Integer.toBinaryString( i )); // 00000000000000001111111111111111 原本高位的16个1都移到了左边,左边空出的位置补0
int hash = h ^ i; // 异或运算
log.info("", Integer.toBinaryString( hash )); // 11111111111111111111111111111111 i高16位没东西,直接照搬 h,低16位,不同为1,相同为 0
key.hashCode()
就是上面的 Object#hashCode
。
- 拿到
hash
值后再将自己的高16位
与自己的低16位
异或,目的是让hash
值变的更散
。 - 打个比喻:(不要硬套牛角尖)
比如我一把抛出16
个弹珠,分别滚进了16
个洞,这就够散
。如果有的洞进了3
个5
个,有的洞空着,这就不够散
。 - 散的意义在于:把
洞
想成数组的index
,抛
这个动作是一种算法,index = 抛( hash值 )
(通过 hash值算出索引),那么最理想的效果肯定是16
个不同的弹珠,分别“抛”
进数组的16
个索引。 - 对
hash
值进行hash ^ (hash >>> 16)
这一套操作其实就是针对抛
这个算法进行优化,尽量避免不同的hash
映射到同一个索引中去。(为什么不同的hash
会算出相同的索引?因为我们从门缝
中看它) - 例如 hashMap 中的
index = ( 数组长度 - 1) & hash
这里的&
操作相当于透过门缝
看hash
。从右边开始,门缝
开多宽,我们就能看到几位hash
。 - 比如数组长度
16
,我们能看到的就是16-1 = 15
,15 转 2 进制1111
(这就是门缝
有4
个二进制位
,当然它左边还有28
个0
就是遮住,我们看不见的部分)。我看看到了hash
值的最后4位,再转成10进制
数就是索引
值了。 - 那么这就有一个问题,hash 有 32 位,只看后 4 位,重复的概率就比较大了。那么我们第 1 步的操作的目的就是尽量减小这种重复。通过
异或
让低16位
也包含了高16位
的特征。这样当两个低位完全相同,高位不一样的hash
算出来的结果就不会相同了。 - 之所以使用
异或
是因为跟与
、或
相比,它的结果是1、0
的概率更平均。与
的结果更倾向于0
,或
的结果更倾向于1
。
JSON.stringify([[0^0, 0^1, 1^0, 1^1], [0&0, 0&1, 1&0, 1&1], [0|0, 0|1, 1|0, 1|1]]);
// [[0,1,1,0], [0,0,0,1], [0,1,1,1]]
- 现在我们再回过头来看第
1
步,是不是有点像我侬词中的泥人。
参考资料
笑虾:Java 集合学习笔记:HashMap
MBA智库百科:哈希算法
CSDN-负债程序猿真:正搞懂hashCode和hash算法
以上是关于Java 学习笔记 HashMap 中的 hash 方法为何要进行异或和位移?的主要内容,如果未能解决你的问题,请参考以下文章