您的位置 首页 java

小米滴滴快手,都在问!HashMap的容量为什么是2的n次方

回答这个问题,我们首先需要知道HashMap是如何存取元素的,为了存取高效,需要把数据分布均匀,这我们就需要分析HashMap的源码,从底层上理解Java作者的意图。

HashMap如何存放元素

HashMap存放元素是通过put()方法来实现的,我们知道定位元素的存放位置,是通过key的hash值,做一定的运算来确定的,而HashMap中的hash()方法,就是来获取hash值的源码如下。

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

这里计算的hash值,并不是直接使用key.hashCode()来获取的值h(后面统一称为h),而是用h与h无符号右移的值做异或运算。

  • h >>> 16

这里先解释下这句话什么意思,>>>其实是无符号右移,什么意思呢,我们看下图就明白了。

将h转换成二进制数,然后无符号右移16位,空缺的用0补齐,看图中字体颜色我们就能清楚的明白这是怎么操作的。

h^ (h >>> 16)的作用

想要知道写HashMap源码大佬的想法,我们还需要来看看HashMap中put()方法的源码。

 public V put(K key, V  value ) {
        return putVal(hash(key), key, value, false, true);
}
final V putVal(int hash, K key, V value, boolean onlyIfAbsent,
                   boolean evict) {
        Node<K,V>[] tab; Node<K,V> p; int n, i;
        if ((tab = table) == null || (n = tab.length) == 0)
            n = (tab = resize()).length;
    //通过位运算找到数组中的下标位置,如果数组中对应下标为空,则可以直接存放下去
        if ((p = tab[i = (n - 1) & hash]) == null)
            tab[i] = newNode(hash, key, value, null);
       ......省略
    }  

我们从源码中可以看出来,计算下标的时候,是通过(n – 1) & hash来计算的,n = tab.length是HashMap的容量,用 HashMap容量减1与hash值做&运算 ,得出下标值。

HashMap默认初始容量为16,16-1=15,15的 二进制 为00000000 00000000 00001111,与hash值做&运算时,结果是只取了最低的后四位,hash值的高位全部归0,只保留了低位值。有的同学,可能不太清楚什么意思,我们举个例子,画个图就清楚了。

  • 假如有个key的hash值,转化为二进制是,01001001 00110010 00011001,获取元素下标时,用HashMap容量减1的二进制与hash值的二进制做&运算,如下图所示。
小米滴滴快手,都在问!HashMap的容量为什么是2的n次方

我们可以看到hash值二进制的高位都补0了,其实上面的计算可以简化为 1111 & 1001,结果为1001,转换成十进制就是9,即下标为9。

  • 当HashMap容量为16=2的4次方时,则是后4位参与&运算
  • 当HashMap容量为32=2的5次方时,则是后5位参与&运算
  • 当HashMap容量为2 时,则是后n位参与&运算,获取元素下标。

因为在大多数情况下,HashMap的容量是小于216,运算得到的下标都是与hashcode的二进制的后16位做&运算的到的值。

要想得到的hash值更加的散列,我们需要把高位和低位都参与运算,这样的算出的hash值,更有特征。如果直接返回hash值,获取元素下标值,参与取模运行的低位,重复性偏高,将高16位和低16位做&运算,这样算出来的hash值,更加分散,hash碰撞的概率就越小,所以就有了h^(h>>>16)。

HashMap容量为2ⁿ的作用

我们知道HashMap底层由数组+(链表或红黑树)来实现,元素都被存放在一个个Entry数组中,存放元素的时候,会根据HashMap中hash()方法计算key的hash值,再根据计算出的hash值,确定元素存到数组的哪个位置,即下标值,一般HashMap的容量不会太大,而hash值并不能直接作为下标值,所以一般根据HashMap的容量减1来取模计算下标值(PS:上面已经介绍了具体做法)。

  • 当容量为2 时,取模的是时候,h&(n-1)与h%n计算的结果相等,而&运算的效率远远比用%效率更高,因为&是直接对内存中的二进制码进行操作,不需要转换为十进制,所以更快。
  • 计算下标值的公式为h&(n-1),当容量为奇数时,n-1则为偶数,转换成二进制,最后一位一定是0,在做&运算计算下标值时,最后一位也一定是0,则奇数位的下标值,则无法取到,如3(0011), 5(0101),7(0111)等,所以容量为偶数时,才能取到奇数下标值。

总结

HashMap的容量为2 主要目的还是为了让元素均匀分布,减少hash碰撞。从HashMap中的hash()方法就知道,让高位和低位都参与运算,让hash值更有特征性,在通过计算的hash值,与容量减一做&运算得到下标值,综合分析,当容量为2 时,元素存放更均匀,效率也更快。 你能看到这里真的很棒,希望你能关注我,我们一起学习,进步!

文章来源:智云一二三科技

文章标题:小米滴滴快手,都在问!HashMap的容量为什么是2的n次方

文章地址:https://www.zhihuclub.com/198239.shtml

关于作者: 智云科技

热门文章

网站地图