Java_HashMap源码解析之hash函数

受众群体

适合了解过HashMap一部分源码,但是不理解其中hash方法为什么那样做的原因。

源码解析

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

这是1.8的版本,之前的版本步骤更多点,但是思路是一致的

h ^ (h >>> 16) 这段代码意思是把h的高16位数字和h的低16位数字异或。
为什么要怎么做呢?
由于Node<K,V>[]数组长度是默认是16,二进制下标是0000~1111,计算Key的数组下标方法是1111 & hash,下标位置是取的hash值的低4位。如果单纯用Object.hashCode()来计算hash值的话,即使两个hash值不一样,但是两者的低4位是一致的,就会造成了hash冲突。为了降低hash冲突,所以采用了低位异或高位的方式来打乱低位,目的是让高位参与hash值的计算。

这里注意下 0 ^ x = x 1 ^ x = ~x,所以当hash值高位存在1的时候会把低位取反,当高位是0的时候,低位不变。如果高16位全是0,则hash值没有任何变化,这也是该算法想要的结果之一。
题外话:根据 0 ^ x = x 1 ^ x = ~x 推断出 0 ^ x ^ x = 0 1 ^ x ^ x = 1 任何数 异或两次相同的数还是等于原来的数。这也是用于对称加密的算法,比较简单,速度快。
PS:之前也会记这些概念,但是自己推算一次后印象非常深刻。


现在搞清了为什么要这样计算hash值,通过解析的过程中又出现了一个新问题:
计算数组下标的掩码每次都是用的都是 (16-1) (32-1) … 都是2的倍数-1,二进制就是1111…1111,也就是说要保证数组长度都是2的倍数,但是平时初始化数组长度的时候又是可以传非2的倍数,那是如何把非2倍数转成2的倍数的呢?

源码在此:

    static final int tableSizeFor(int cap) {
        int n = cap - 1;
        n |= n >>> 1;
        n |= n >>> 2;
        n |= n >>> 4;
        n |= n >>> 8;
        n |= n >>> 16;
        return (n < 0) ? 1 : (n >= MAXIMUM_CAPACITY) ? MAXIMUM_CAPACITY : n + 1;
    }

这个就厉害了,先代入9试试。

  1. n = 9 - 1 = 8 1001 - 1 = 1000
  2. n |= n >>> 1 1000 | 0100 = 1100
  3. n |= n >>> 2 1100 | 0011 = 1111
  4. n |= n >>> 4 1111 | 0000 = 1111
  5. … 重复以上步骤
  6. 最后 + 1就等于2的N次方

该方法会把从高位开始第一个出现1之后所有的位都变成1
那为什么第一步要减一呢?我猜测:如果不减一,比如1000最后结果是1111+1=10000=16,这个结果翻了一倍,如果减一,算出来结果就是1000还是8。

总结

这种工匠精神是好的工程师都有的一种特性,我也正好有这种特性,但是工作久了后,慢慢地有点丢失了这种精神,以后我会多加注意,保持这种精神。

  • 0
    点赞
  • 1
    收藏
    觉得还不错? 一键收藏
  • 0
    评论
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

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

抵扣说明:

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

余额充值