面试官Q1:为什么HashMap的长度一定是2的次幂呢?

通过前面一篇文章我们知道了,HashMap的数据结构,也知道了什么是Hash冲突,如果定位到的数组位置不含链表(当前entry的next指向null),那么对于查找,添加等操作很快,仅需一次寻址即可;如果定位到的数组包含链表,对于添加操作,其时间复杂度为O(n),首先遍历链表,存在即覆盖,否则新增;对于查找操作来讲,仍需遍历链表,然后通过key对象的equals方法逐一比对查找。所以,性能考虑,HashMap中的链表出现越少,性能才会越好。那么HashMap底层到底做了什么,使得Hash值散列、均匀分布呢?
以JDK1.8为例,查看下面一段代码:

1public V put(K key, V value{
2    return putVal(hash(key), key, valuefalsetrue);
3}
然后再点进putVal 方法,则会看到有下面的代码:
1final V putVal(int hash, K key, V value, boolean onlyIfAbsent,
2                   boolean evict)
 
{
3        Node<K,V>[] tab; Node<K,V> p; int n, i;
4        if ((tab = table) == null || (n = tab.length) == 0)
5            n = (tab = resize()).length;
6        if ((p = tab[i = (n - 1) & hash]) == null)
7            tab[i] = newNode(hash, key, value, null);
我们需要关注地方是这么一段代码:
1tab[i = (n - 1) & hash]
这里是计算数组索引下标的位置

&为二进制中的与运算,它的运算特点是,两个数进行&,如果相同则为1,不同则为0。 

因为hashMap 的数组长度都是2的n次幂 ,那么对于这个数再减去1,转换成二进制的话,就肯定是最高位为0,其他位全是1 的数。

那以数组长度为8为例(默认HashMap初始数组长度是16),那8-1 转成二进制的话,就是0111 。 那我们举一个随便的hashCode值,与0111进行与运算看看结果如何:

数字8减去1转换成二进制是0111,即下边的情况:

 1第一个key:      hashcode值:10101001    
2   与0111进行&运算        &      0111                                      
3                                0001  (十进制为1
4   ------------------------------------------                           
5 第二个key:      hashcode值:11101000    
6   与0111进行&运算        &       0111      
7                                 0000  (十进制为0
8--------------------------------------------               
9 第三个key:      hashcode值:11101110    
10   与0111进行&运算        &       0111      
11                                 0110  (十进制为6


这样得到的数,就会完整的得到原hashcode 值的低位值,不会受到与运算对数据的变化影响。

数字7减去1转换成二进制是0110,即下边的情况:
 1第一个key:      hashcode值:10101001    
2   与0111进行&运算        &       0110                                      
3                                 0000  (十进制为0
4   ------------------------------------------                           
5 第二个key:      hashcode值:11101000    
6   与0111进行&运算        &       0110      
7                                 0000  (十进制为0
8--------------------------------------------               
9 第三个key:      hashcode值:11101110    
10   与0111进行&运算        &       0111      
11                                 0110  (十进制为6
通过上边可以看到,当数组长度不为2的n次幂 的时候,hashCode 值与数组长度减一做与运算 的时候,会出现重复的数据,因为不为2的n次幂 的话,对应的二进制数肯定有一位为0 ,这样,不管你的hashCode 值对应的该位,是0还是1 ,最终得到的该位上的数肯定是0,这带来的问题就是HashMap上的数组元素分布不均匀,而数组上的某些位置,永远也用不到。如下图所示:

为何HashMap的数组长度一定是2的次幂?_HashMap

这将带来的问题就是你的HashMap 数组的利用率太低,并且链表可能因为上边的(n - 1) & hash 运算结果碰撞率过高,导致链表太深。(当然jdk 1.8已经在链表数据超过8个以后转换成了红黑树的操作,但那样也很容易造成它们之间的转换时机的提前到来),所以说HashMap的长度一定是2的次幂,否则会出现性能问题。

转自:https://mp.weixin.qq.com/s/HowuTmZRw1RQhCo-I3r0rA