HashMap
有两个重要属性:size
和load factor
。我浏览了Java文档,并说0.75f
是初始加载因子。但我找不到它的实际用途。
有人可以描述我们需要设置负载因子的不同场景以及针对不同情况的一些样本理想值吗?
答案 0 :(得分:238)
documentation解释得非常好:
HashMap的一个实例有两个影响其性能的参数:初始容量和负载因子。容量是哈希表中的桶数,初始容量只是创建哈希表时的容量。加载因子是在自动增加容量之前允许哈希表获取的完整程度的度量。当哈希表中的条目数超过加载因子和当前容量的乘积时,哈希表将被重新哈希(即,重建内部数据结构),以便哈希表具有大约两倍的桶数。 / p>
作为一般规则,默认负载系数(.75)在时间和空间成本之间提供了良好的权衡。较高的值会减少空间开销,但会增加查找成本(反映在HashMap类的大多数操作中,包括get和put)。在设置其初始容量时,应考虑映射中的预期条目数及其加载因子,以便最小化重新散列操作的数量。如果初始容量大于最大条目数除以负载因子,则不会发生任何重新连接操作。
与所有性能优化一样,最好避免过早地优化事物(即没有瓶颈所在的硬数据)。
答案 1 :(得分:134)
HashMap
的默认初始容量为16,载荷系数为0.75f(即当前地图大小的75%)。负载系数表示HashMap
容量应加倍的水平。
例如容量和负载系数的乘积为16 * 0.75 = 12
。这表示在将第12个键值对存储到HashMap
后,其容量变为32。
答案 2 :(得分:31)
实际上,根据我的计算,"完美"负载系数接近log 2(~0.7)。虽然任何小于此的负载系数都会产生更好的性能。我认为.75可能已经脱光了。
证明:
可以避免链接,并通过预测是否利用分支预测 桶是空的还是没有。如果存在概率,则桶可能是空的 空虚超过.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...
答案 3 :(得分:24)
HashMap增加容量所需的容量是多少?
负载系数默认为初始容量的0.75(16),因此在容量和容量增加之前,25%的桶将是空闲的。这使得许多新桶具有指向它们的新哈希码,只是在桶数量增加之后才存在。
如果你将加载因子设置为1.0,那么可能会发生一些非常有趣的事情。
假设您要将对象x添加到hashmap中,其hashCode为888&在你的hashmap中,代表哈希码的桶是免费的,所以对象x 被添加到桶中,但现在再次说你是否要添加另一个对象y,其hashCode也是888,那么你的对象y将得到添加肯定但在桶的末尾(因为桶只是LinkedList实现存储密钥,值和下一个)现在这会对性能产生影响!由于您执行查找时,对象y 不再存在于存储桶的头部,因此所用的时间不会是 O(1)这次取决于同一个桶里有多少件物品。这被称为哈希冲突的方式&当加载因子小于1时,甚至会发生这种情况。
降低负载系数 =更多免费存储桶= 更少碰撞机会 =高性能=高空间要求。
如果我错了,请纠正我。
答案 4 :(得分:17)
答案 5 :(得分:2)
如果水桶太满了,那么我们必须仔细查看
一个很长的链表。
那有点不合时宜。
所以这是一个示例,其中我有四个存储桶。
到目前为止,我的HashSet中有大象和r。
这是一个很好的情况,对吧?
每个元素都有零个或一个元素。
现在,我们在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可以使用一定比例或
答案 6 :(得分:1)
我会选择n * 1.5或n +(n>> 1)的表格大小,这样可以在没有分割的情况下提供.66666的加载因子,这在大多数系统上都很慢,特别是在便携式系统上硬件中没有分区。
答案 7 :(得分:0)
对于HashMap DEFAULT_INITIAL_CAPACITY = 16 和 DEFAULT_LOAD_FACTOR = 0.75f
这意味着HashMap中的所有条目的最大数量= 16 * 0.75 = 12 。当添加第十三个元素时,HashMap的容量(数组大小)将加倍!
完美的插图回答了这个问题:
图片是从这里拍摄的:
https://javabypatel.blogspot.com/2015/10/what-is-load-factor-and-rehashing-in-hashmap.html
答案 8 :(得分:0)
对负载因子和哈希表的完整理解是here