一尘不染

为什么 Java HashMap 加载因子是 0.75?

java

我不明白为什么 Java HashMap 加载因子是 0.75。如果我理解得很好,负载因子的公式是 n/m,其中 n 是键数,m 是哈希表中的位置数。

由于 HashMap 利用存储桶(即链表)来存储值,因此负载因子 > 1 可能没有问题,所以我不清楚为什么将负载因子设置为 0.75


阅读 120

收藏
2022-02-28

共1个答案

一尘不染

我不知道答案,但我可以引导您了解设计这种数据结构的人的想法。

假设一个“好的”散列函数,并且足够大,那么对于给定的负载因子,一个桶中恰好包含个元素的概率由泊松分布给出:nnλλkk

P(k,λ)=e−λλkk!P(k,λ)=e−λλkk!

因此,例如,如果,那么只包含一个元素的桶的比例(即那些不需要链接的桶;我不确定 Java 的 HashMap 究竟是如何实现它的)是:λ=1λ=1

P(1,1)=1e≈36.8%P(1,1)=1e≈36.8%

对于,它是:λ=0.75λ=0.75

P(1,0.75)≈35.4%P(1,0.75)≈35.4%

差别不大。但是,这是按桶而不是按项目衡量的。不需要遍历链(因此是无循环的)的成功查询的比例是。对于,即为,但对于,即为。P(1,λ)/λP(1,λ)/λλ=1λ=136.8%36.8%λ=0.75λ=0.7547.2%47.2%

因此,通过将从减少到,不需要遍历链的成功查询的比例从略高于三分之一增加到略低于一半。λλ110.750.75

但它可能会使用更多的内存。还有多少?

我将假设某种优化的表示形式,其中哈希表是单词的数组,空桶表示为空指针,具有单个元素的桶表示为指向对象的指针,以及具有多个元素的存储桶表示为指向链表的指针。我将假设一个链表节点的大小为 3 个单词:对象头(Java 语义要求)、指向项目的指针和“下一个”指针。nn

因此,您可以将空桶的开销成本视为单词,一个包含一个项目的桶是单词,一个包含个项目的桶是单词。1111k>1k>11+3k1+3k

我会让你自己计算出细节,如果,我计算出每个存储项目的开销约为个字,对于,个字存储的项目。内存开销的差异小于λ=1λ=12.8962.896λ=0.75λ=0.752.9162.9160.7%0.7%

(练习:在这些假设下,对于什么值是最小化的内存开销?我会说它大于并且小于。它不是;如果是的,这将是我答案的一部分!)λλ1212110.750.75

因此,在这些假设下,您似乎确实会获得更有效的查找,并且只需要稍微增加内存开销。这只是您可以考虑的众多权衡之一。

我的猜测是,Java 库实现者执行了一些与此类似的粗略计算,并进行了一些实验来确定,并发现这是可以接受的。

2022-02-28