HashMap中的indexFor方法分析

在整理HashMap的工作原理时,发现它调用了 indexFor(int h, int length) 方法来计算Entry对象保存在 table中的数组索引值:

static int indexFor(int h, int length) {
    return h & (length-1);
}

它没有对hash表的长度取余而使用了位运算来得到索引,这是为什么呢,顿生怀疑~

分析

//获取当前table的长度
int newCapacity = table.length;
//若长度小于目标长度,则扩展为之前的2倍
while (newCapacity < targetCapacity)
    newCapacity <<= 1;

HashMap的初始容量和扩容都是以2的次方来进行的,那么length-1换算成二进制的话肯定所有位都为1,就比如2的3次方为8,length-1的二进制表示就是111, 而按位与计算的原则是两位同时为“1”,结果才为“1”,否则为“0”。所以h& (length-1)运算从数值上来讲其实等价于对length取模,也就是h%length。

如果不满足前提条件“HashMap的初始容量和扩容都是以2的次方来进行的”,会发生什么问题呢?

假设当前table的length是15,二进制表示为1111,那么length-1就是1110,此时有两个hash值为8和9的key需要计算索引值,计算过程如下:

8的二进制表示:1000
8&(length-1)= 1000 & 1110 = 1000,索引值即为8;

9的二进制表示:1001
9&(length-1)= 1001 & 1110 = 1000,索引值也为8;

这样一来就产生了相同的索引值,也就是说两个hash值为8和9的key会定位到数组中的同一个位置上形成链表,这就产生了碰撞。

而查询的时候需要遍历这个链表,这样就降低了查询的效率。同时,我们也可以发现,当数组长度为15的时候,hash值会与length-1(1110)进行按位与,那么最后一位永远是0,而0001,0011,0101,1001,1011,0111,1101这几个位置永远都不能存放元素了,会造成严重的空间浪费,更糟的是这种情况下,数组可以使用的位置比数组长度小了很多,这意味着进一步增加了碰撞的几率,减慢了查询的效率。

因此可以看出,只有当数组长度为2的n次方时,不同的key计算得出的index索引相同的几率才会较小,数据在数组上分布也比较均匀,碰撞的几率也小,相对的,查询的时候就不用遍历某个位置上的链表,这样查询效率也就较高了。

此外,位运算快于十进制运算,hashmap扩容也是按位扩容,这样同时也提高了运算效率。

你可能感兴趣的:(java)