为什么HashMap的加载因子一定是0,java教程视频网

我们知道,HashMap 是通过拉链法来解决哈希冲突的。

为了减少哈希冲突发生的概率,当 HashMap 的数组长度达到一个临界值的时候,就会触发扩容(可以点击链接查看 HashMap 的扩容机制),扩容后会将之前小数组中的元素转移到大数组中,这是一个相当耗时的操作。

这个临界值由什么来确定呢?

临界值 = 初始容量 * 加载因子

一开始,HashMap 的容量是 16:

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

加载因子是 0.75:

static final float DEFAULT_LOAD_FACTOR = 0.75f;

也就是说,当 16*0.75=12 时,会触发扩容机制。

为什么加载因子会选择 0.75 呢?为什么不是0.8、0.6呢?

这跟统计学里的一个很重要的原理——泊松分布有关。

是时候上维基百科了:

泊松分布,是一种统计与概率学里常见到的离散概率分布,由法国数学家西莫恩·德尼·泊松在1838年时提出。它会对随机事件的发生次数进行建模,适用于涉及计算在给定的时间段、距离、面积等范围内发生随机事件的次数的应用情形。

阮一峰老师曾在一篇博文中详细的介绍了泊松分布和指数分布,大家可以去看一下。

链接:https://www.ruanyifeng.com/blog/2015/06/poisson-distribution.html

具体是用这么一个公式来表示的。

等号的左边,P 表示概率,N表示某种函数关系,t 表示时间,n 表示数量。

在 HashMap 的 doc 文档里,曾有这么一段描述:

Because TreeNodes are about twice the size of regular nodes, we

use them only when bins contain enough nodes to warrant use

(see TREEIFY_THRESHOLD). And when they become too small (due to

removal or resizing) they are converted back to plain bins. In

usage

【一线大厂Java面试题解析+后端开发学习笔记+最新架构讲解视频+实战项目源码讲义】

浏览器打开:qq.cn.hn/FTf 免费领取

s with well-distributed user hashCodes, tree bins are

rarely used. Ideally, under random hashCodes, the frequency of

nodes in bins follows a Poisson distribution

(http://en.wikipedia.org/wiki/Poisson_distribution) with a

parameter of about 0.5 on average for the default resizing

threshold of 0.75, although with a large variance because of

resizing granularity. Ignoring variance, the expected

occurrences of list size k are (exp(-0.5) * pow(0.5, k) /

factorial(k)). The first values are:

0: 0.60653066

1: 0.30326533

2: 0.07581633

3: 0.01263606

4: 0.00157952

5: 0.00015795

6: 0.00001316

7: 0.00000094

8: 0.00000006

more: less than 1 in ten million

大致的意思就是:

因为 TreeNode(红黑树)的大小约为链表节点的两倍,所以我们只有在一个拉链已经拉了足够节点的时候才会转为tree(参考TREEIFY_THRESHOLD)。并且,当这个hash桶的节点因为移除或者扩容后resize数量变小的时候,我们会将树再转为拉链。如果一个用户的数据的hashcode值分布得很均匀的话,就会很少使用到红黑树。

理想情况下,我们使用随机的hashcode值,加载因子为0.75情况,尽管由于粒度调整会产生较大的方差,节点的分布频率仍然会服从参数为0.5的泊松分布。链表的长度为 8 发生的概率仅有 0.00000006。

虽然这段话的本意更多的是表示 jdk 8中为什么拉链长度超过8的时候进行了红黑树转换,但提到了 0.75 这个加载因子——但这并不是为什么加载因子是 0.75 的答案。

为了搞清楚到底为什么,我看到了这篇文章:

参考链接:https://segmentfault.com/a/1190000023308658

里面提到了一个概念:二项分布(二哥概率论没学好,只能简单说一说)。

在做一件事情的时候,其结果的概率只有2种情况,和抛硬币一样,不是正面就是反面。

为此,我们做了 N 次实验,那么在每次试验中只有两种可能的结果,并且每次实验是独立的,不同实验之间互不影响,每次实验成功的概率都是一样的。

以此理论为基础,我们来做这样的实验:我们往哈希表中扔数据,如果发生哈希冲突就为失败,否则为成功。

我们可以设想,实验的hash值是随机的,并且经过hash运算的键都会映射到hash表的地址空间上,那么这个结果也是随机的。所以,每次put的时候就相当于我们在扔一个16面(我们先假设默认长度为16)的骰子,扔骰子实验那肯定是相互独立的。碰撞发生即扔了n次有出现重复数字。

然后,我们的目的是啥呢?

就是掷了k次骰子,没有一次是相同的概率,需要尽可能的大些,一般意义上我们肯定要大于0.5(这个数是个理想数,但是我是能接受的)。

于是,n次事件里面,碰撞为0的概率,由上面公式得:

这个概率值需要大于0.5,我们认为这样的hashmap可以提供很低的碰撞率。所以:

这时候,我们对于该公式其实最想求的时候长度s的时候,n为多少次就应该进行扩容了?而负载因子则是 n / s n/s n/s的值。所以推导如下:

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

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

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

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

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

抵扣说明:

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

余额充值