繁体   English   中英

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

[英]What is the significance of load factor in HashMap?

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

有人可以描述我们需要设置负载因子的不同场景以及不同情况下的一些样本理想值吗?

文档很好地解释了它:

HashMap 的实例有两个影响其性能的参数:初始容量和负载因子。 容量是哈希表中的桶数,初始容量就是哈希表创建时的容量。 负载因子是衡量哈希表在其容量自动增加之前允许达到多满的指标。 当哈希表中的条目数超过负载因子和当前容量的乘积时,重新哈希表(即重建内部数据结构),使哈希表具有大约两倍的桶数。

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

与所有性能优化一样,最好避免过早优化(即没有关于瓶颈位置的硬数据)。

HashMap默认初始容量为 16,加载因子为 0.75f(即当前地图大小的 75%)。 负载因子表示HashMap容量应该在什么级别增加一倍。

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

实际上,根据我的计算,“完美”负载因子更接近 log 2 (~ 0.7)。 尽管任何小于此的负载因子都会产生更好的性能。 我认为 0.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...

什么是负载因子?

HashMap 增加容量需要消耗多少容量?

为什么是负载因子?

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

现在为什么要保留许多空闲桶?保留空闲桶对性能有什么影响?

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

假设您将一个对象 x 添加到您的哈希图中,其 hashCode 为 888 & 在您的哈希图中,表示哈希码的存储桶是空闲的,因此对象 x被添加到存储桶中,但现在再次说明您是否要添加另一个哈希码为 y 的对象也是 888 那么你的对象 y 肯定会被添加到存储桶的末尾(因为存储桶只不过是存储键、值和下一个的链表实现),现在这会对性能产生影响! 由于如果您执行查找,您的对象 y不再存在于桶的头部,因此这次花费的时间不会是O(1),这取决于同一个桶中有多少项目。 顺便说一下,这称为哈希冲突,甚至在您的加载因子小于 1 时也会发生这种情况。

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

更低的负载系数= 更多的空闲桶 =更少的碰撞机会= 高性能 = 高空间需求。

如果我在某处错了,请纠正我。

文档

负载因子是衡量哈希表在其容量自动增加之前允许达到多满的指标

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

对于 HashMap DEFAULT_INITIAL_CAPACITY = 16DEFAULT_LOAD_FACTOR = 0.75f这意味着 HashMap 中所有条目的最大数量 = 16 * 0.75 = 12 当第 13 个元素被添加时,HashMap 的容量(数组大小)将增加一倍! 完美插图回答了这个问题: 在此处输入图片说明 图像取自此处:

https://javabypatel.blogspot.com/2015/10/what-is-load-factor-and-rehashing-in-hashmap.html

如果桶装得太满,那么我们必须查看

一个很长的链表。

这有点违背了这一点。

所以这里有一个例子,我有四个桶。

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

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

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

现在我们将另外两个元素放入我们的 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 个桶中只看

一个元素,

我需要看两个。

但至少我不用看大象、獾和

猫。

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

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 的大小,也就是说我们将添加更多的桶,

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

负载因子。

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

大于 loadFactor。

此时,每个bucket的平均元素数

是 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 的大小需要两个步骤。

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

现在我将有 12 个桶。

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

但是改变的桶数是12,

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

5 除以 12 约为 0.42,这远低于我们的

负载因子,

所以我们现在没事了。

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

现在错误的存储桶。

以大象为例。

大象在 2 号桶中,因为

大象中的人物

是 8。

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

这就是为什么它最终排在第 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,

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

平均,我需要重新插入。

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

所有的元素。

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

这是指目前站立的水桶

在我开始调整所有内容之前。

注意我还没有创建一个新的链表数组。

我只是将桶重命名为 oldBuckets。

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

现在创建一个新数组

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

就像第一次那样。

现在我需要重新插入,

我将遍历所有旧存储桶。

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

那是一个桶。

我将通过那个桶并获取其中的每个元素

桶。

现在我要把它重新插入到 newBuckets 中。

我会得到它的哈希码。

我会弄清楚它是哪个索引。

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

字符串和

我会将它添加到那个新存储桶中。

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

列表或桶。

一个自调整大小的 HashSet 可以使用一些比率或

我会选择一个大小为 n * 1.5 或 n + (n >> 1) 的表,这将使负载因子为 0.66666~ 无除法,这在大多数系统上都很慢,尤其是在没有除法的便携式系统上硬件。

完整了解负载因子和重新散列就在这里

暂无
暂无

声明:本站的技术帖子网页,遵循CC BY-SA 4.0协议,如果您需要转载,请注明本站网址或者原文地址。任何问题请咨询:yoyou2525@163.com.

 
粤ICP备18138465号  © 2020-2024 STACKOOM.COM