new hashmap 初始大小_极致 HashMap(3)魔法数

b2bc257740e92e72a5cc58371615b233.png

至少 HashMap 是数组。

一个直击灵魂的问题出现了,初始化的时候表大一点好还是小一点好?

44dadec0744ebb8a7d58015b9dae05a8.png

Java 中数组最大是多少?看一下数组的 length 属性就可以了。

——它的 length 属性是 32 位的有符号整数,那么取值范围是 -2^31 到 2^31-1 ,最大是 2GB。

为什么 length 的属性不是 long 呢?

——如果它是long型的,那么最大长度是 2 的 63 次幂。目前内存没那么大。


说回 HashMap 的初始化,Java给出的存储结构肯定是要即适用于大量数据也要适用于少量数据。调试的时候一个工具类直接把内存占满了就不合适了。

所以初始化的时候表不能太大


那多大?

这就要说回小表了,小表有什么问题?

—— 哈希碰撞概率大幅度上升。

如果表大小是 1 ,那么每一个节点都一定碰撞,整个数据结构退化成链表。

并且如果表大小固定,在使用的过程中只要插入的数据足够多,后期使用的时候又退化成链表了。


这个逻辑是这样的,

  1. 不可能初始化一个巨大的表
  2. 所以要初始化一个小表
  3. 不论 hashcode 有多么精致,表小就说明后期一定会产生大量的 hash 碰撞
  4. 扩容

因为我们不得不用小表,所以在表中数据到达一定数量的时候一定要扩容,不然毫无意义。

在接受了小表+扩容这个解决的前提之下

小表的大小是多少?

—— 16 ,一个神奇的魔法数。


/**
 * 表的大小一定要是2的幂
 */
static final int DEFAULT_INITIAL_CAPACITY = 1 << 4; // aka 16
the main reason to move to power-of-two sizes was that bit masking is faster than integer division. This is not without adverse consequences, which are explained by one of the original authors: Joshua Bloch

2 的幂可以给后续的某些操作提供便利性。可是,

  • 2 的幂有哪些好处?
  • 为什么就是 16 呢?8 ?32 ?

先解决第一个问题。

首先方便算扩容。

new = old << 1

这么做直接就是 x 2 ,算得快。

再说扩容几倍合适?一般地,容量 x 2 怎么着也是足够的了。

第二个是方便算 i 。从 hashcode 里获得了一个大数字要变成 i , 这个 i 是 0 到长度减一之内的一个数字。怎么做?

—— mod。

i = hash % (tab.length - 1)

然而,聪明的小朋友会发现这个取余操作非常慢。事实上可以用与操作优化,

i = (tab.length - 1) & hash

这是一种取余的优化方案。

这是因为如果 n 是 2 的幂,这种方式正好可以割下了尾部的几个连续的二进制位数。这个逻辑和掩码一模一样。如下所示,

hash = 01001111
n = 00001000
n - 1 = 00000111
i = (n - 1) & hash = 00000111

因为 n 是 2 的幂, 所以可以完整地切割尾部。i 可以取 0000 到 0111 中的所有值。

可如果 n 是别的类型的数?

hash = 01001111
n = 00000111
n - 1 = 00000110
i = (n - 1) & hash = 00000110

这漏了一位。

i 只能那个取 0000 到 0111 中的所有偶数。这意味着 Table 一半的容量永远空。

这两个好处一结合,所以 n 为 2 的幂。


第二个问题要简单的多。

首先,扩容这个操作到底在做什么?

不仅仅是 Table 增加一倍。

之前定位算法是 ( tab.length - 1 ) & hash 。

现在 n 变成 2n了。之前存储的数据全都要重新用 2n 来算 i 重新定位重新处理哈希碰撞。

(事实上,当 n 变成 2n 之后,旧数据放置在同一个 i 上的概率是 50% ,另外 50% 是放到 n + i 上面)

扩容是非常麻烦的,麻烦在于要重新算 hash 算 i 。

b1b5ab23d9c8390a8315ec87d6c20dd4.png

所以尽量别扩容。

把表的大小设置成 2 或者 4 ,是不是给自己找麻烦?觉得自己电脑很牛逼就喜欢扩容,没意义的。

HashMap 给的答案是 16 ,因为他们觉得 8 也小。

一定的经验参数,又综合了各种考虑,定表大小初始值为16。


下一节要开始说 1.7 的具体实现了。基本上看到现在能明白的,就已经对 1.7 的逻辑了如指掌了,下一节了解一下专有名词。

  • 0
    点赞
  • 0
    收藏
    觉得还不错? 一键收藏
  • 0
    评论
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

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

抵扣说明:

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

余额充值