HashMap中负载因子的意义是什么?


232

HashMap具有两个重要属性:sizeload factor。我浏览了Java文档,它说的0.75f是初始负载因子。但是我找不到它的实际用途。

有人可以描述需要设置负载系数的不同情况是什么,以及针对不同情况的一些理想样本值吗?

Answers:


266

文档对其进行了很好的解释:

HashMap的实例具有两个影响其性能的参数:初始容量和负载因子。容量是哈希表中存储桶的数量,初始容量只是哈希表创建时的容量。负载因子是在自动增加其散列表容量之前允许散列表获得的满度的度量。当哈希表中的条目数超过负载因子和当前容量的乘积时,哈希表将被重新哈希(即,内部数据结构将被重建),因此哈希表的存储桶数约为两倍。

通常,默认负载因子(.75)在时间和空间成本之间提供了一个很好的权衡。较高的值会减少空间开销,但会增加查找成本(在HashMap类的大多数操作中都得到体现,包括get和put)。设置其初始容量时,应考虑映射中的预期条目数及其负载因子,以最大程度地减少重新哈希操作的次数。如果初始容量大于最大条目数除以负载系数,则将不会发生任何重新哈希操作。

与所有性能优化一样,最好避免过早地进行优化(例如,没有关于瓶颈所在的硬数据)。


14
建议使用其他答案capacity = N/0.75以避免重新散列,但是我最初的想法刚刚确定load factor = 1。这种方法会有弊端吗?为什么会客座率影响get()put()运营成本?
supermitch

19
统计上,具有条目数=容量的负载因子= 1哈希表将具有大量冲突(=当多个键产生相同哈希时)。发生冲突时,查找时间会增加,因为在一个存储桶中将有> 1个匹配条目,因此必须逐一检查密钥是否相等。一些详细的数学运算:preshing.com/20110504/hash-collision-probabilities
atimb 2014年

8
我没有跟随你@atimb; loadset属性仅用于确定何时增加存储大小,对吗?-一个负载集如何增加哈希冲突的可能性?-散列算法不知道地图中有多少项,或者它多久获取一次新的存储“存储桶”等。对于相同大小的任何对象集,无论它们如何存储,都应具有重复哈希值的概率相同...
BrainSlugs83

19
如果地图的大小较大,则哈希冲突的可能性较小。例如,如果地图的大小为4,则将具有哈希码4、8、16和32的元素放置在同一存储桶中,但是如果地图的大小大于32,则每个项目都将拥有自己的存储桶。在此示例中,初始大小为4且负载系数为1.0(4个存储桶,但单个存储桶中的所有4个元素)的地图的平均速度将比负载因子为0.75(8个存储桶,两个存储桶已填充-元素“ 4”和元素“ 8”,“ 16”,“ 32”)。
2014年

1
@Adelin查找费用随着更高的加载因子而增加,因为更高的值会发生更多的冲突,而Java处理冲突的方式是通过使用数据结构将具有相同哈希码的项目放在同一存储桶中。从Java 8开始,此数据结构是一个二进制搜索树。这使得查找最坏情况的时间复杂度为O(lg(n)),如果添加的所有元素碰巧都具有相同的哈希码,则出现最坏情况。
Gigi Bayte

141

HashMap拍摄的默认初始容量为16,负载系数为0.75f(即当前地图尺寸的75%)。负载系数表示HashMap应将容量提高一倍的级别。

例如,容量和负载系数的乘积为16 * 0.75 = 12。这表示将第12个键-值对存储到中之后HashMap,其容量变为32。


3
尽管您的答案很明确,但是能否请您说一下存储12个键值对之后容量是否变为32,还是添加第13个条目时容量改变然后插入条目。
userab

这是否意味着铲斗数量增加了2个?
LoveMeow

39

实际上,根据我的计算,“完美”的负载系数更接近log 2(〜0.7)。尽管任何小于此的负载因子都会产生更好的性能。我认为.75可能已被取消。

证明:

通过预测存储桶是否为空,可以避免链接并利用分支预测。如果存储桶为空的可能性超过0.5,则该存储桶可能为空。

让s代表大小,n代表增加的键数。使用二项式定理,存储桶为空的概率为:

P(0) = C(n, 0) * (1/s)^0 * (1 - 1/s)^(n - 0)

因此,如果少于

log(2)/log(s/(s - 1)) keys

随着s达到无穷大,并且如果添加的键数达到P(0)= .5,则n / s迅速接近log(2):

lim (log(2)/log(s/(s - 1)))/s as s -> infinity = log(2) ~ 0.693...

4
数学书呆子FTW!可能将的数值.75四舍五入到最接近的易于理解的分数log(2),并且看起来像幻数一样少。我很乐意看到JDK默认值的更新,并在其实现上方加上注释:D
解码时间为

2
我真的很想喜欢这个答案,但是我是JavaEE开发人员,这意味着数学从来都不是我真正的强项,所以我对您写的内容几乎一无所知
searchengine27

28

什么是负载系数?

HashMap增加容量将要用尽的容量?

为什么要使用负载系数?

负载因子默认为初始容量的0.75(16),因此在容量增加之前25%的存储桶将是空闲的,这使得许多带有新哈希码的新存储桶在存储容量增加之后就存在。桶数。

现在为什么要保留许多免费存储桶?保留免费存储桶对性能有何影响?

如果将加载因子设置为1.0,则可能会发生一些非常有趣的事情。

假设您正在将对象x添加到其hashCode为888的hashmap中,并且在您的hashmap中,表示该hashcode的存储桶是free,因此该对象x被添加到存储桶中,但是现在再次说一遍,如果您要添加另一个其hashCode为的对象y同样是888,那么您的对象y肯定会在存储桶的末尾添加(因为存储桶不过是存储key,value和next的linkedList实现而已),这对性能有影响!如果执行查找,由于对象y不再存在于存储桶的头部,因此花费的时间不会是O(1)这次取决于同一个存储桶中有多少个物品。顺便说一下,这称为哈希冲突,甚至会在您的加载因子小于1时发生。

性能,哈希冲突和加载因子之间的相关性?

较低的负载系数 =更多的空铲斗= 更少的碰撞机会 =高性能=占用空间大。

如果我在某个地方不对,请纠正我。


2
您可以添加一些有关如何将hashCode简化为范围为1- {count bucket}的数字的信息,因此它本身并不是存储桶的数量,但是hash算法的最终结果涵盖了范围更大。HashCode不是完整的哈希算法,它足够小,可以轻松地重新处理。因此,这里没有“可用存储桶”的概念,而是“可用存储桶的最小数量”,因为您可以将所有元素存储在同一存储桶中。而是,它是您的哈希码的键空间,等于容量*(1 / load_factor)。40个元素,0.25负载系数= 160个铲斗。
user1122069 '16

我认为从中查找对象的时间LinkedList称为,Amortized Constant Execution Time+O(1)+
Raf

19

文档中

负载因子是在自动增加其容量之前允许哈希表获得的满度的度量

这实际上取决于您的特定要求,没有用于指定初始负载系数的“经验法则”。


该文件还说;“作为一般规则,默认负载因子(.75)在时间和空间成本之间提供了很好的折衷。” 因此,对于任何不确定的人,默认设置是一个很好的经验法则。
ferekdoley


2

如果水桶太满,那么我们必须仔细检查

一个很长的链表。

这有点失误。

所以这是一个示例,其中我有四个存储桶。

到目前为止,我的HashSet中没有大象和badge。

这是一个很好的情况,对吗?

每个元素具有零个或一个元素。

现在,我们在HashSet中又添加了两个元素。

     buckets      elements
      -------      -------
        0          elephant
        1          otter
         2          badger
         3           cat

这也不错。

每个桶只有一个元素。所以,如果我想知道,其中是否包含熊猫?

我可以很快查看1号存储桶,它不是

还有

我知道它不在我们的收藏中。

如果我想知道它是否包含猫,我看看水桶

3号

我找到猫,我很快就知道它是否在我们的猫中

采集。

如果我添加考拉呢,那还不错。

             buckets      elements
      -------      -------
        0          elephant
        1          otter -> koala 
         2          badger
         3           cat

也许现在而不是在1号存储区中只看

一个元素,

我需要看两个。

但至少我不必看大象,badge和

猫。

如果我再次寻找熊猫,它只能放在桶里

1号和

除水獭外,我不必再看其他东西了

考拉。

但是现在我把鳄鱼放在1号水桶里,你可以

看看可能会发生什么。

那如果1号桶越来越大

更大,那么我基本上必须仔细检查所有

那些要寻找的元素

应该在存储桶编号1中存储的内容。

            buckets      elements
      -------      -------
        0          elephant
        1          otter -> koala ->alligator
         2          badger
         3           cat

如果我开始向其他存储桶添加字符串,

是的,问题在每一个领域都越来越大

单桶。

我们如何阻止水桶太满?

这里的解决方案是

          "the HashSet can automatically

        resize the number of buckets."

HashSet意识到水桶越来越

太满了。

失去了所有查找的优势

元素。

而且它只会创建更多的存储桶(通常是之前的两倍),

然后将元素放入正确的存储桶中。

所以这是我们的基本HashSet实现,带有单独的

连锁。现在,我将创建一个“自动调整大小的HashSet”。

该HashSet将意识到这些存储桶是

变得太饱了

它需要更多的水桶。

loadFactor是我们的HashSet类中的另一个字段。

loadFactor表示每个元素的平均数量

桶,

在此之上我们要调整大小。

loadFactor是时空之间的平衡。

如果水桶太满,我们将调整大小。

当然,这需要时间,但是

如果水桶很脏,可能会节省我们的时间

多一点空。

让我们来看一个例子。

这是一个HashSet,到目前为止,我们已经添加了四个元素。

大象,狗,猫和鱼。

          buckets      elements
      -------      -------
        0          
        1          elephant
         2          cat ->dog
         3           fish
          4         
           5

至此,我决定将loadFactor,

阈,

没关系,每个存储桶的平均元素数量

与,是0.75。

桶数为buckets.length,即6,

此时,我们的HashSet具有四个元素,因此

当前大小为4。

我们将调整HashSet的大小,即添加更多的存储桶,

当每个存储桶中的平均元素数超过

loadFactor。

那是当当前大小除以buckets.length时

大于loadFactor。

此时,每个存储桶的平均元素数

是4除以6。

4个元素,6个存储桶,即0.67。

这小于我设定的0.75的阈值,所以我们

好的。

我们不需要调整大小。

但是,现在让我们添加土拨鼠。

                  buckets      elements
      -------      -------
        0          
        1          elephant
         2        woodchuck-> cat ->dog
         3           fish
          4         
           5

土拨鼠最终将进入第3斗。

此时,currentSize为5。

现在,每个存储桶的平均元素数

是currentSize除以buckets.length的值。

那5个元素除以6个存储桶就是0.83。

这超过了0.75的loadFactor。

为了解决这个问题,为了使

水桶也许有点

比较空的,这样操作就可以确定是否

桶包含

元素会稍微复杂一些,我想调整大小

我的HashSet。

调整HashSet的大小需要两个步骤。

首先,我将桶数量翻倍,我有6个桶,

现在我要有12个水桶。

请注意,我设置为0.75的loadFactor保持不变。

但是更改的存储桶数是12

元素数量保持不变,为5。

5除以12大约是0.42,这在我们的

loadFactor,

所以我们现在还好。

但是我们还没有完成,因为其中一些要素

现在错了。

例如大象。

大象在2号存储桶中,因为

大象中的人物

是8。

我们有6个水桶,而8减6是2。

这就是为什么它排名第二的原因。

但是现在我们有12个桶,8 mod 12是8,所以

大象不再属于2号存储桶。

大象属于第8斗。

那土拨鼠呢?

土拨鼠是引发整个问题的人。

土拨鼠最终进入了第3斗。

因为9 mod 6是3。

但是现在我们做9 mod 12。

9 mod 12是9,土拨鼠进入第9个铲斗。

您会看到所有这些优点。

现在,第3个存储区只有两个元素,而之前有3个。

这是我们的代码

我们的HashSet带有单独的链接

没有做任何调整大小。

现在,这是我们使用调整大小的新实现。

大部分代码是相同的,

我们仍然要确定它是否包含

价值已经。

如果不是,那么我们将找出它是哪个存储桶

应该进入

然后将其添加到该存储桶,并将其添加到该LinkedList。

但是现在我们增加了currentSize字段。

currentSize是跟踪数字的字段

HashSet中的元素集。

我们要增加它,然后再看

在平均负载下

每个存储桶中的平均元素数。

我们将在这里进行该划分。

我们必须在此处进行一些投射以确保

我们得到了双倍。

然后,我们将平均负载与现场进行比较

我设置为

例如,当我创建此HashSet时为0.75

loadFactor。

如果平均负载大于loadFactor,

这意味着每个存储桶上的元素太多

平均而言,我需要重新插入。

所以这是我们重新插入方法的实现

所有元素。

首先,我将创建一个名为oldBuckets的局部变量。

指的是当前站立的水桶

在开始调整大小之前。

注意我还没有创建新的链表列表。

我只是将存储桶重命名为oldBuckets。

现在记得水桶是我们班上的一个领域,我要

现在创建一个新的数组

链表,但这将有两倍的元素

就像第一次一样。

现在我需要重新插入

我将遍历所有旧桶。

oldBuckets中的每个元素都是字符串的LinkedList

那是一个水桶。

我将遍历该存储桶并获取其中的每个元素

桶。

现在,我将其重新插入newBuckets中。

我将获取其hashCode。

我将找出它是哪个索引。

现在我得到了新的存储桶,即新的LinkedList

字符串和

我将其添加到新的存储桶中。

回顾一下,我们已经看到,HashSets是Linked数组

列表或存储桶。

自调整大小的HashSet可以使用某种比率或


1

我会选择n * 1.5或n +(n >> 1)的表大小,这样得出的负载因子为.66666〜(不进行除法),这在大多数系统上都很慢,尤其是在不进行除法的便携式系统上硬件。

By using our site, you acknowledge that you have read and understand our Cookie Policy and Privacy Policy.
Licensed under cc by-sa 3.0 with attribution required.