【HashMap】初始容量大小, 计算方法tableSizeFor详解, 为什么我们设置完HashMap的大小后, 默认变成2的幂次方大小

 

当我们创建一个HashMap容器时, 有时为了节省空间, 会指定容器的默认大小.如下图, 虽然我指定了HashMap默认大小是12, 但实际HashMap默认大小是16, 因为设置HashMap默认大小必须是2的幂次方法. 所以实际大小是16, 但是为什么是这样呢? 下面我们来分析一下.

首先我看下构造方法里,请看下图. 在HashMap(int initialCapacity)构造方法中, 实际调用this(initialCapacity, DEFAULT_LOAD_FACTOR)这个构造方法, DEFAULT_LOAD_FACTOR就是负载因子(0.75), 这个是后期计算链表数组大小使用的.我们这里不需要关心.

然后我们看下this(initialCapacity, DEFAULT_LOAD_FACTOR)这个构造方法, 发现它调用了tableSizeFor方法

在我们学习tableSizeFor方法之前, 我们需要了解几个知识点, <<(左移), >>(右移), >>>(无符号右移), &(与), |(或), ^(非), 这里大家自己查阅一下资料吧, 我就不在这里讲解了. 下面我们来分析一下tableSizeFor方法. 下图是我们要讲解的tableSizeFor方法和该方法中使用到的常量值等, 注释信息的一个翻译(翻译的不好大家见谅(●'◡'●))

1.首先我们先说下 int n = cap -1; 这里为什么要减一?

 因为在HashMap中, 初始容量大小默认为2的幂次方, 即1, 2,4,8,16,32....一直到MAXIMUM_CAPACITY,

 如果不减一, 当cap=1时, 初始容量为2, cap=2时初始容量=4(其实2就够用), cap=4时, 初始容量=8(其实4就够用), 这样会导致空间的浪费.

2. 这时候可能有人会问了, 为什么不减一, cap=1的时候, 初始容量就是2了呢?

接下来我们讲一下>>>(无符号右移了)和|(或)运算

注: 我转成二进制只写了8位, 如果你是32位系统, 就是32个0或1, 如果你是64位系统就是64个0或1, 这里我们用不到那么多位,就不写那么长了.只用了8位.

2.1 首先我们先说下, 不减一的情况下
// 例 cap = 4;
int n = cap;   // 第一步: n = 4, 转换成二进制, 0000 0100

n |= n >>> 1;  // 第二步: 相当于n = n | n >>> 1;
            // n >>> 1 相当于 0000 0100 往右移一位, 变成 0000 0010
            // n | n >>> 1 相当于 0000 0100 | 0000 0010, 变成 0000 0110 转成十进制等于6
            // |(或)的意思就是有1则1,

n |= n >>> 2;  // 第三步: 相当于n = n | n >>> 2;
            // n >>> 2 相当于 0000 0110 往右移两位, 变成0000 0001
            // n | n >>> 2 相当于 0000 0110 | 0000 0001, 变成 0000 0111 转成十进制等于7

n |= n >>> 4;  // 第四步: 相当于n = n | n >>> 4;
            // n >>> 4 相当于 0000 0111 往右移四位, 变成0000 0000
            // n | n >>> 2 相当于 0000 0111 | 0000 0000, 变成 0000 0111 转成十进制等于7

n |= n >>> 8;  //同第三步
n |= n >>> 16; //同第三步

// 下面这部是两个三元运算, 我们给拆分一下
// 即:
// 公式1: b = n >= MAXIMUM_CAPACITY) ? MAXIMUM_CAPACITY : n + 1
// n >= MAXIMUM_CAPACITY吗? 大于 b = MAXIMUM_CAPACITY 小于b = n+1, b=8
//
// 公式2: n = n < 0) ? 1 : b
// n < 0 吗 ? 小于n = 1, 大于n = b 即n = 8
return (n < 0) ? 1 : (n >= MAXIMUM_CAPACITY) ? MAXIMUM_CAPACITY : n + 1;

通过以上解析可以看出, 为什么要减一了. 因为, 如果不减一, 当我们初始容器大小是2的幂次方时, 实际容器初始化大小是2的幂次方+1, 所以这里要对我们输入的初始化容器大小减一.

2.2 然后我们说下, 减一的情况下
// 例 cap = 4;
int n = cap-1; // 第一步: n = 3, 转换成二进制, 0000 0011

n |= n >>> 1;  // 第二步: 相当于n = n | n >>> 1;
            // n >>> 1 相当于 0000 0011 往右移一位, 变成 0000 0001
            // n | n >>> 1 相当于 0000 0011 | 0000 0001, 变成 0000 0011 转成十进制等于3
            // |(或)的意思就是有1则1,

n |= n >>> 2;  // 第三步:相当于n = n | n >>> 2;
            // n >>> 2 相当于 0000 0011 往右移两位, 变成0000 0000
            // n | n >>> 2 相当于 0000 0011 | 0000 0000, 变成 0000 0011 转成十进制等于3

n |= n >>> 4;  //同第三步
n |= n >>> 8;  //同第三步
n |= n >>> 16; //同第三步

// 下面这部是两个三元运算, 我们给拆分一下
// 即:
// 公式1: b = n >= MAXIMUM_CAPACITY) ? MAXIMUM_CAPACITY : n + 1
// n >= MAXIMUM_CAPACITY吗? 大于 b = MAXIMUM_CAPACITY 小于b = n+1, b=4
//
// 公式2: n = n < 0) ? 1 : b
// n < 0 吗? 小于n = 1, 大于n = b 即n=4
return (n < 0) ? 1 : (n >= MAXIMUM_CAPACITY) ? MAXIMUM_CAPACITY : n + 1;

4. 总结: 通过我们对tableSizeFor方法的解析,我们明白了, 原来HashMap是通过>>>(无符号右移) 2的幂次方数(2, 4, 8, 16), 加上|(或)运算来保证初始大小总是2的幂次方, 所以当我们通过构造方法设置了HashMap的初始大小以后, 如果我们设置的不是2的幂次放, 也会通过tableSizeFor将初始容量变成2的幂次方,即我们new HashMap(12), 实际HashMap的大小是16. 通过对cap减一保证HashMap使用合理的初始容量, 达到空间的利用率.

5. 课外知识: 看了上面的讲解, 是不是可以自己计算出 MAXIMUM_CAPACITY的大小了, MAXIMUM_CAPACITY = 1 << 30 我们这里用32位的二进制来计算, 64位无非就是在前面再加32个0, 我们这里用不到就不加了.

即: 0000 0000 0000 0000 0000 0000 0000 0001 左移30位 变成 0100 0000 0000 0000 0000 0000 0000 0000

0100 0000 0000 0000 0000 0000 0000 0000 转成十进制等于1073741824

6. 疑问点:

 问题1: 为什么HashMap的初始容量, 一定要是2的幂次方呢? 为什么要这么设计呢?

【HashMap】为什么HashMap的大小一定要是2的幂次方呢?_尛_的博客-CSDN博客

 问题2: 上面我们分析的只是初始容量值的一个计算, 那HashMap什么时候初始化的, 并指定容器的大小呢?

  • 1
    点赞
  • 3
    收藏
    觉得还不错? 一键收藏
  • 0
    评论

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

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

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

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

余额充值