为什么hash函数能降哈希碰撞?2024-9-23

static final int hash(Object key){

        int h;

        return (key ==null)?0:(h=key.hashCode())^(h>>>16);

}

在hash函数中,先调用了key的hashCode()方法,这将返回一个int类型的哈希值,比如说字符串的hashCode。

public int hashCode()(

        int h = hash;

        if (h == 0 && value.length >0){

                char val[] = value;

                for (int i =0;i<value.length;i++{

                        h =31 *h+ val[i];

                }

                hash = h;

        }

        return h;

}

int的范围是-2147483648-2147483647,加起来大概40亿上下的浮动。

只要key的hashCode方法设计的比较合理,一般是很难出现碰撞的。

但问题是,不可能直接搞一个40亿长度的数组啊,那也太铺张浪费了。

我们一般会设置一个较小的数组长度,比如说HashMap的数组初始大小才16,当发现容量不满足的时候再扩容,避免浪费。

那当数组长度比较小的时候,我们就需要设计一种比较巧妙的hash算法,来避免发生哈希冲突,尽可能地让元素均匀地分布在数组当中。

要达到这个目的,HashMap在两方面下足了功夫,第一个就是数组的长度必须是2的整数次幂,这样可以保证hash&(n-1)的结果能均匀地分布在数组中。

其作用就相当hash%n,n为数组的长度,比如说数组长度是16,hash值为20,那么20%16 =4,也就是说20这个元素应该放在数组的第4个位置;hash值为23,那么23%16=7,也就是说23这个元素应该放在数组的第7个位置。

&操作的结果就是哈希值的高位全部归零,只保留n个低位,用来做数组下标访问。

比如说hash&($2^{4} -1$)的结果实际上是取hash的低4位,这四位能表示的取值范围刚好是0000到1111,也就是0到15,正好是数组长度为16的下标范围。

以初始长度16为例,16-1=15

2进制表示是0000 0000 0000 0000 0000 0000 0000 1111.和某个哈希值做&运算,结果就是截取了最低的四位

那问题又来了,那么大一个哈希值,也只取最后4为,不就等于哈希值的高位都丢弃了吗?

比如说1111 1111 111 1  1 1 1 1  1 1 1 1,取最后4为,也就是1111.

比如说11101111101111111111111111111111111,取最后4为,也是11111

不就发生哈希冲突了吗?

这时候hash函数(h=key.hashCode())^(h>>>16)就派上用场了呀.

将哈希值无符号右移16位,意味着哈希值的高16为被移到了低16位的位置。

这样,原始哈希值的高位和低16位就可以参与到最终用于索引计算的低位中。

选择16位是因为它是32位整数的一半,这样处理既考虑了高位的信息,又没有完全忽视低位原本的信息,尝试达到一个平衡的状态。

举个例子(数组长度为16)

第一个数:h1=0001 0010 0011 0100 0101 0110 0111 1000

第二个数:h2=0001 0010 0011 0101 0101 0110 0111 1000

如果没有hash函数,直接取低4为,那么h1和h2的低4位都是1000,也就是两个数都会放在数组的第8个位置。

来看一个hash函数的处理过程。

对于第一个数h1的计算:

原始:0001 0010 0011 0100 0101 0110 0111 1000

右移 :0000 0000 0000 0000 0001 0010 0011 0101

异或--------------------------------------------------------

结果:0001 0010 0011 0101 0100 0100 0100 1101

通过上述计算,我们可以看到h1和h2经过h^(h>>>16)操作后得到了不同的结果。

现在,考虑数组长度为16时(需要最低4位来确定索引)

对于h1的最低4位1100(十进制中为12)

对于h2的最低4位是1101(十进制中为13)

这样,h1和h2就会被分别放在数组的第12个位置和第13个位置上,避免了哈希冲突。

评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

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

抵扣说明:

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

余额充值