HashMap的哈希/扰动函数的设计,为什么能降低hash碰撞?

HashMap的哈希/扰动函数的设计

总结来说,hash函数的作用就是:将 key 的 hashCode 值进行处理,得到最终的哈希值

当new 一个 HashMap,并通过 put 方法添加一个元素时,会用到这个方法。

public class Main {
    public static void main(String[] args) {
        HashMap map=new HashMap();
        map.put("sanguo",45);
    }
}

我们查看put方法:

public V put(K key, V value) {
        return putVal(hash(key), key, value, false, true);
    }

可以看到hash(key)方法,下面,我们来看hash方法具体实现。代码如下:

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

下面是对该方法的一些解释:

  • 参数 key:需要计算哈希码的键值。
  • key == null ? 0 : (h = key.hashCode()) ^ (h >>> 16):这是一个三目运算符,如果键值为 null,则哈希码为 0(依旧是说如果键为 null,则存放在第一个位置);否则,通过调用hashCode()方法获取键的哈希码,并将其与右移 16 位的哈希码进行异或运算。
  • ^ 运算符:异或运算符是 Java 中的一种位运算符,它用于将两个数的二进制位进行比较,如果相同则为 0,不同则为 1。
  • h >>> 16:将哈希码向右移动 16 位,相当于将原来的哈希码分成了两个 16 位的部分。最终返回的是经过异或运算后得到的哈希码值。
  • 为什么要进行异或操作呢?因为对于 hashCode 的高位和低位,它们的分布是比较均匀的,如果只是简单地将它们加起来或者进行位运算,容易出现哈希冲突,而异或操作可以避免这个问题。

为什么能降低hash碰撞

因为:哈希函数是先拿到key 的hashcode,是⼀个32位的int类型的数值,然后让hashcode的高16位和低16位进行异或操作。

具体来看:

key. hashCode()函数调用的是key键值类型自带的哈希函数,返回int型散列值。int值范围为-2147 483648~2147483647,加起来大概40亿的映射空间。 只要哈希函数映射得比较均匀松散,⼀般应用是很难出现碰撞的。但问题是⼀个40亿长度的数组,内存是放不下的。假如HashMap数组的始大小才16,就需要用之前需要对数组的长度取模运算,得到的余数才能用来访问数组下标。源码中模运算就是把散列值和数组长度 - 1 做⼀个 " 与 & " 操作,位运算比取余 % 运算要快。数组下标i如下。

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

这也正好解释了为什么HashMap的数组长度要取2的整数幂。因为这样数组长度 - 1正好相当于⼀个 “低位掩码”。 与操作的结果就是散列值的⾼位全部归零,只保留低位值,用来做数组下标访问。以初始长度16为例,16 - 1 = 15。2 进制表⽰是 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 1111 。和某个散列值做与操作如下,结果就是截取了最低的四位值。

这样是要快捷⼀些,但是新的问题来了,就算散列值分布再松散,要是只取最后几位的话,碰撞也会很严重。这时候扰动函数的价值就体现出来了,看⼀下扰动函数的示意图:

  • 6
    点赞
  • 9
    收藏
    觉得还不错? 一键收藏
  • 0
    评论

“相关推荐”对你有帮助么?

  • 非常没帮助
  • 没帮助
  • 一般
  • 有帮助
  • 非常有帮助
提交
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值