【hashmap计算hashcode时为什么要把高位右移16位】

  • Post author:
  • Post category:其他



写在前面:

1、如有错误请看官斧正。

2、建议把例子复制出去,自己用各种

位运算

算一下,然后也像我这样把异同处标出来,这样能在自己计算的过程中有更深的领悟。

一、hashmap计算hashcode时为什么要把高位右移16位


有一个hashcode值的二进制形式

key.hashcode()


二进制:

0010-0101 1100-0100 0010-0101 1011-0110


十进制:

633611702

上面这么写只是为了便于观察,并不遵循任何规范。

key.hashcode() >>> 16 (无符号右移16位)

这个地方是个人理解,不一定准确

亦或运算:不同为1,相同为0  异或运算能更好的保留各部分的特征(变化的地方01都有)

与运算:  有0则0,全1则1  结果偏向于0 (变化的地方都变0了)

或运算:  有1则1,全0则0  结果偏向于1(变化的地方都变1了)

二、再看hashmap是怎么取元素存放位置的

可以看到在取元素存放位置时,1.7和1.8是用的同一方法,

都是

hash

^(n-1)【哈希值与上entry数组长度-1】

小白提问1:为啥要&entryLength-1呢

抛开上面提到的右移16位问题,你想想,一个对象取完hash值了,它得往entry数组里放吧,但是数组长度又是有限的,不可能一个萝卜一个坑。


以默认初始长度为16为例

想把一个编号大于16的数,在16个格子里找个位置放下,要是你自己设计会咋做呢(不能乱放)

绝大多数人都会选择取余法,就是:编号%格子数

小白举例: 17%16 = 1  放在第一个格子。

取余操作涉及到底层汇编和运算器电路设计,虽然我本科是电子信息的,但早就把知识还给老师了。

大家只要知道一个结论,就是取余运算没位运算快就行。

经过上面的科普,大家已经知道取余运算没位运算快了(手动狗头脸)。

本着安全第一,效率至上的原则就只能用位运算了。


几个重要因素:

  1. 参与运算的值:hashcode、entry数组长度
  2. 运算方式:位运算 (与&,或|,非~)
  3. 预期结果:有规律可循,起码结果要和取余运算相同。

那就开始设计吧~

(呃….我也没啥好的设计,还是分析下源码吧)

Entry长度-1 = 15;

15的二进制形式

0000-0000 0000-0000 0000-0000 0000-1111

十进制取余算法:


633611702 % 16 = 6


633611702 % 32 = 22

到这为止有三个问题继续解答

  1. 为啥数组长度要是2的n次幂?

  2. 为啥entry数组长度要-1?

  3. 为啥要右移16位进行亦或?

这不是呼之欲出么,呼你妹啊,我到现在还都不知道为啥。

个人理解:

第一个问题

  1. 优化需要:hash & (length -1) 在结果上与 取余算法一致
  2. 符合计算机思维,在计算机中跟数据相关的数值基本都是2的n次

如:MySQL页的大小为16kb

操作系统文件管理系统一次IO读取的数据大小同样为1页(4kb)相当于8个扇区(512bytes )

第二个问题

  1. 数组长度不减1的话,低位都是0,与操作下低位都没法参与运算,而且还有结果越界的情况(如上面的反例)
  2. 减1再与运算,可以使低位都参与运算,增加散列程度。

第三个问题

请各位看官再回去搂一眼最上面的三个例子,右移后再亦或,高位和低位做了混合,在之后的hash & (length-1) 中高位就也参与进运算了,增加了散列程度。

文章知识点与官方知识档案匹配,可进一步学习相关知识