HashMap中的求余算法

110 阅读2分钟

在上篇文章中,我们提到了HashMap在添加元素时取得hashCode的算法。

  1. 得到key值的hashCode();
  2. 做异或操作;
  3. 取模运算;

第一步很好理解,也就是调用key.hashCode()这个算法,取得其原始的哈希值。

第二步,首先根据传入的key值生成一个哈希值,随后将该哈希值取高16位后与其本身进行异或运算。这个运算本身只是一个扰动,是为了让元素分布更加均匀,尽量减少哈希冲突。同时,让高16位与低16位进行异或,那么他们都参与到了扰动当中,变相保留了原本高位数据特征的同时增加了低位的随机性。

第三步,进行取模运算。这里就是进行性能优化的一个关键。

n=2kn=2^k时,amodn=a&(n1)a\:mod\:n=a\&(n-1)
那这个是怎么来的呢?

假设n=8n=8,我们要计算a=10a=10amodna\: mod \:n

  • n=8n=8的二进制为n=00001000n=00001000(假设是8位二进制,实际在java中int是32位,这里为了方便起见就不再赘述)。那么如果一个数的二进制比nn大,那么其对nn取余数时,二进制中比1000高的高位就应该全部置0。类比十进制就很容易想通,对于十进制数来说,如果一个数比如1234对10取余数,那么只需要 令a=10a=10,此时a的余数就是4,相当于把123这高三位全部置0。同理,二进制为1010,如果对二进制的1000取余,那就是把高1位置0。
  • 那么知道了这个之后再看,为什么直接与(n1)(n-1)取与就能够得到余数。已知nn2k2^k,那么nn的二进制就必然为高1位为1,其余为0的形式。如:10(2),100(4),1000(8)...10(2),100(4),1000(8)...那么(n1)(n-1)也就会是高1位为0,其余为1的形式。如:01(1),011(3),0111(7)...01(1),011(3),0111(7)... 显然,将n1n-1与原先的数相&,就可以直接将所需置0的位置置零,最后所得到的就是余数。

这样一来就将取模的操作转换成了相与,效率得到了很大程度的提升。