jdk1.8之后HashMap的容量为什么必须为2^n

1.首先HashMap的数据结构

一维数组 + 链表/红黑树(当链表长度大于8则转换为红黑树);

2.那么问题来了:怎么确定数组的索引下标?

(当put一个key-val时, 应该放在数组的哪个位置?)

这个地方就要求尽量达到"均匀散布", why?(均匀散布性越低, ->就会使得链表越长/红黑树越高)

为什么链表越短越好, ->因为链表查找元素只能循环遍历, 数组才能索引快速访问;

=>至此, 目的一致, 以"均匀散布"为目标!!!

 ->那么往固定长度容器里面放入各种不同元素,怎么才能保证"不超出容器长度"且"公平散布"呢??

->然后HashMap采用了取模(hash(key) % length),即 hash(key) / length = 商......"余数"

(注: 取模公式: a % b = a ÷ b 的"余数")

         (这里"取模"肯定能保证不超出容器长度, 但是保证"公平散布"难道是因为hash值比较公平的值???  >存疑-期待大佬指点)

 结论:"取模"运算的目的

1.取到key的hash值(int);

2.用hash值除以数组的长度;

3.得到的"余数"就是元素在数组中的"均匀散布"索引位置(数组下标)

注: 此"余数"肯定不可能超过被除数(数组长度)

3.另"取模"运算是算术运算, 性能低下

所以为了效率 ->应"算术运算"转换为二进制的位运算

因公式: x / 2^n = x >>> n; // 可自行验证(10进制也是如此)

(即: 当被除数为2^n次方时, 除法运算可转换为右移运算 (乘法则左移))

        且 "x" 右移出去的部分就是"余数"(即: "x"右移出去的n位 == "余数" == 取模结果);

               原因: 二进制逢2进1;  (十进制也是同样的,   1234 / 10^2 = 1.34(右移2位))

结论: 取模运算(取余数) == 右移运算中被移出去的部分(前提: 除数必须是2^n)

4. 那现在怎么取"余数"呢??

(以上"右移"运算得到的是"商", 不是"余数")

从第3步可得:

取模: x % (2^n)  ==  x>>> n 的右移出去的部分

=> x % (2^n)  ==  x的二进制的后n位

怎么取一个数的 "后n位" 呢???

很显然:  x & (前面全是0,后面n个1) == x的后n位 (因为: "&"运算规则: 都为1->1; 否->0)

又 2^n - 1 刚好满足这个条件(前面全是0, 后面跟n个1)(因为二进制, 同理十进制应是10^n)

原因: 2^n == 00010000"(前面一个1,后面跟n个0) => (2^n-1) == 00001111 (前面全是0,后面跟n个1)

推导过程如下:

( 往map中写的key的hash值->以下简称hash, map的数组长度length= 2^n)

HashMap数组下标 == hash % 2^n  (注释: 取模运算)

== hash / 2^n的余数 == hash >>> n 这个右移运算的"移出去的部分"

== hash & (2^n-1)  (注释: 取hash的后n位)

== hash & (length - 1)

这不最终结果就出来了; 和源码写的一模一样的 ;)    源码如下

   (另: 很显然能看到,  "余数"的计算仅和"hash的后n位有关", 根本没用到hash的前几位, => 所以jdk1.8后, 在object的hash方法计算的hash值又做了一次高低位的亦或运算, 目的是:混合高低位,使得数组下标计算时利用到整个hash值,而不是只有后n位参与), 源码如下:

所以HashMap必须保证数组的长度length == 2^n;  就算你设置了初始化容量也不会生效, 他会自动往上扩容至2的幂;

"欢乐的时光很快就过去了, 又到时候讲拜拜";)

评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

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

抵扣说明:

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

余额充值