真正搞懂hashCode和hash算法【转载】

hashCode的概念

hashCode的设计初衷是 提高哈希容器的性能

equals的效率是没有hashCode高的,
所以先判断hashcode是否相同,不相同则对象肯定不等,相同才再用equals判断
ps:重写equals()方法时候一定要重写hashCode()方法

hash算法

1. hash()整体认知

HashMap的hash算法 设计超级超级巧妙

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

hashMap的默认初始容量是16,也就是有16个桶。通过这个hash()得到hash,计算(n - 1) & hash得到HashMap在put对象时该放到哪个桶。

2. hash()中特殊操作的目的

  • 数组长度-1、^运算、>>>16是为了让key在hashmap的桶中尽可能分散。
    否则会造成过多的hash冲突,key几乎全部怼到同一个桶里,分布很不分散。
  • 更关键的是,如果不数组长度 - 1,会返回桶索引n,会越界。
  • 用&而不用取模%是为了提高计算性能

3. 特殊操作的原理

3.1 计算桶索引时:

1)与运算中,为什么用(n - 1) & hash,而不是n&hash?

比如,在默认长度n=16的情况下——

  • 首先明确两点:
    ① 16的二进制数是10000,16-1=15的二进制数是1111
    ② 与运算的性质是,同1才为1
  • 说明任何数和16进行与运算的结果,除了倒数第5位,其他位不论hash是何值 结果肯定是0。
  • 而和15进行与运算时,由于15的后四位是1,结果也受hash的后四位影响。

所以,让hash和n-1做与运算,可能性更多,结果也更分散。

2)为什么要用与运算(n-1)&hash计算下标,而不是用取模hash%n?

与运算的特性:当b为2的n次方时,a%b=a&(b-1)

HashMap的长度总是2^n(不知道的去看它的扩容机制),而位运算肯定速度更快,因此~。

3.2 hash()内部

1)为什么要进行^运算,|运算、&运算不行吗?

相比其他位运算而言, ^ 运算的下标更分散。

2)为什么要>>>16,>>>15不行吗?

>>>16表示无符号右移16位,位数不够,高位补0

其实>>>16和 ^ 运算是相辅相成的关系,这一套操作是为了保留hash值高16位和低16位的特征

  • 因为数组长度(按默认的16来算)减1后的二进制码低16位永远是1111,我们肯定要尽可能的让1111和hash值产生联系,
  • 但是很显然,如果只是1111&hash值的话,&运算是都为1才为1,1111我们肯定是改变不了的,只有从hash值入手。1111只会与hash值的低四位产生联系,也就是说这种算法出来的值只保留了hash值低四位的特征,前面还有28位的特征全部丢失了;
  • 所以hashMap作者采用了 key.hashCode() ^ (key.hashCode() >>> 16) 这个巧妙的扰动算法【key的hash值经过无符号右移16位,再与key原来的hash值进行 ^ 运算】,就能很好的保留hash值的所有特征,这种离散效果才是我们最想要的。

上面这两段话就是理解>>>16和 ^ 运算的精髓所在,如果没看懂,建议你休息一会儿再回来看,总之记住,目的都是为了让数组下标更分散

再补充一点点,其实并不是非得右移16位右移8位右移12位都能起到很好的扰动效果,但是hash值的二进制码是32位,所以最理想的肯定是折半咯,雨露均沾。

评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

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

抵扣说明:

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

余额充值