HashMap指定数组大小为什么必须是2的n次幂

HashMap指定数组大小为什么必须是2的n次幂

1.版本化的序列号

private static final long serialVersionUID = 362498820763181265L;

2.集合的初始化容器(必须是2的n次幂)

static final int DEFAULT_INITIAL_CAPACITY = 1 << 4; // aka 16

为什么必须是2的n次幂,如果不是2的2次幂会什么样子?

​ 通过Hashmap添加一个元素的时候我们知道,hashmap需要根据key的hash去确定数组中的具体下标,HashMap为了提升搞笑,尽量较少碰撞,那么他就要尽量把数据均匀分配,让每个链表长度大致相同,这个实现就是把数据存到哪个链表中的算法。

​ 这个算法其实就是取模,hashcode%length(数组长度),计算机直接求余数不如位运算效率高,所以源码中做了优化,使用hashcode&(length-1),hashcode%length等于hashcode&(length-1)的前提是length是2的n次幂。

2^4   就是10000
2^4-1 就是11112的n次方的计算方式
3 &8-100000011  3
00000111  7
------------
00000011  3  索引为3

00000010  2
00000111  7
------------
00000010  2  索引为2
    
不是2的n次方的计算方式
3 &9-100000011  2
00001000  8
------------
00000000  0  索引为0
    
2 &9-100000010  2
00001000  8
------------
00000000  0  索引为0

计算出的索引特别容易相同,及其容器发生hash碰撞,导致其余数组空间很大程度上并没有存储数据,链表或者红黑树过长,效率降低。

总结:

​ 由上面可以看出来,HashMap是根据key来确定数组的下标的,如果n为2幂次方就可以保证数据均匀的插入,如果不是2的幂次方,可能数组的一些位置永远也不会插入数据,这样就浪费了数组的空间还加大了hash冲突,通过%来确定位置效率远远不如&运算,但是如果使用&的话必须要保证length(数组长度)是2的n次幂。HashMap使用2的n次幂放就是为了保证数据均匀分布减少hash冲突,如果hash冲突越大,代表数组中的一个链或者红黑树长度越大,这样会降低hashmap的性能,

如果创建Hashmap对象的时候,指定的数组长度不是2幂次方,那么HashMap会通过位移运算得到比输入数字大的数组最近的2次幂

查看下我们给指定数组长度的HashMap源码

public HashMap(int initialCapacity) {
    this(initialCapacity, DEFAULT_LOAD_FACTOR);
}

我们去看这个构造方法

public HashMap(int initialCapacity, float loadFactor) {
    //如果我们给的长度小于0,就抛出异常
    if (initialCapacity < 0)
        throw new IllegalArgumentException("Illegal initial capacity: " +
                                           initialCapacity);
    //如果我们给的长度大于2^30 那么就把2^30次方赋值给它,意思就是我们的长度必须小于2^30
    if (initialCapacity > MAXIMUM_CAPACITY)
        initialCapacity = MAXIMUM_CAPACITY;
    if (loadFactor <= 0 || Float.isNaN(loadFactor))
        throw new IllegalArgumentException("Illegal load factor: " +
                                           loadFactor);
    this.loadFactor = loadFactor;
    //设置数组长度
    this.threshold = tableSizeFor(initialCapacity);
}
/**
 * Returns a power of two size for the given target capacity.
 * 或运算都是0的时候为0有1的时候为1
 * 假设cap传入的是10
 */
static final int tableSizeFor(int cap) {
    //这里-1是为了防止出现我们传入了16 还进行了右移运算,假如我们输入成16 通过-1然后最后右移算出来是16
    int n = cap - 1;		 
    /*
    	00001001	9
    	00000100	4	9 >>> 1
    	--------------
    	00001101	13	9 |= 4
    */
    n |= n >>> 1;		
    /*
    	00001101	13
    	00000011	3
    	---------------
    	00001111	15
    */
    n |= n >>> 2;
    /*
     	00001111	15
     	00000000	0
     	--------------
     	00001111	15
     */
    n |= n >>> 4;
       /*
     	00001111	15
     	00000000	0
     	--------------
     	00001111	15
     */
    n |= n >>> 8;
       /*
     	00001111	15
     	00000000	0
     	--------------
     	00001111	15
     */
    n |= n >>> 16;
    return (n < 0) ? 1 : (n >= MAXIMUM_CAPACITY) ? MAXIMUM_CAPACITY : n + 1;
}

总结

​ 如果给的值不是2的幂次方,那么就通过底层右移操作,变成大于你指定的数字最小的二次幂

​ 我们传入的数组长度大小不能超过2^30

  • 2
    点赞
  • 2
    收藏
    觉得还不错? 一键收藏
  • 打赏
    打赏
  • 0
    评论

“相关推荐”对你有帮助么?

  • 非常没帮助
  • 没帮助
  • 一般
  • 有帮助
  • 非常有帮助
提交
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包

打赏作者

哇塞大嘴好帅(DaZuiZui)

你的鼓励将是我创作的最大动力

¥1 ¥2 ¥4 ¥6 ¥10 ¥20
扫码支付:¥1
获取中
扫码支付

您的余额不足,请更换扫码支付或充值

打赏作者

实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值