HashMap
具有两个重要属性:size
和load factor
。我浏览了Java文档,它说的0.75f
是初始负载因子。但是我找不到它的实际用途。
有人可以描述需要设置负载系数的不同情况是什么,以及针对不同情况的一些理想样本值吗?
HashMap
具有两个重要属性:size
和load factor
。我浏览了Java文档,它说的0.75f
是初始负载因子。但是我找不到它的实际用途。
有人可以描述需要设置负载系数的不同情况是什么,以及针对不同情况的一些理想样本值吗?
Answers:
该文档对其进行了很好的解释:
HashMap的实例具有两个影响其性能的参数:初始容量和负载因子。容量是哈希表中存储桶的数量,初始容量只是哈希表创建时的容量。负载因子是在自动增加其散列表容量之前允许散列表获得的满度的度量。当哈希表中的条目数超过负载因子和当前容量的乘积时,哈希表将被重新哈希(即,内部数据结构将被重建),因此哈希表的存储桶数约为两倍。
通常,默认负载因子(.75)在时间和空间成本之间提供了一个很好的权衡。较高的值会减少空间开销,但会增加查找成本(在HashMap类的大多数操作中都得到体现,包括get和put)。设置其初始容量时,应考虑映射中的预期条目数及其负载因子,以最大程度地减少重新哈希操作的次数。如果初始容量大于最大条目数除以负载系数,则将不会发生任何重新哈希操作。
与所有性能优化一样,最好避免过早地进行优化(例如,没有关于瓶颈所在的硬数据)。
实际上,根据我的计算,“完美”的负载系数更接近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...
.75
四舍五入到最接近的易于理解的分数log(2)
,并且看起来像幻数一样少。我很乐意看到JDK默认值的更新,并在其实现上方加上注释:D
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时发生。
较低的负载系数 =更多的空铲斗= 更少的碰撞机会 =高性能=占用空间大。
如果我在某个地方不对,请纠正我。
LinkedList
称为,Amortized Constant Execution Time
并+
以O(1)+
对于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
如果水桶太满,那么我们必须仔细检查
一个很长的链表。
这有点失误。
所以这是一个示例,其中我有四个存储桶。
到目前为止,我的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可以使用某种比率或
capacity = N/0.75
以避免重新散列,但是我最初的想法刚刚确定load factor = 1
。这种方法会有弊端吗?为什么会客座率影响get()
和put()
运营成本?