【问题标题】:Performance of HashMap with different initial capacity and load factor不同初始容量和负载因子的HashMap性能
【发布时间】:2010-11-22 09:12:14
【问题描述】:

这是我的情况。我正在使用两个 java.util.HashMap 将一些常用数据存储在运行在 Tomcat 上的 Java Web 应用程序中。我知道每个 Hashmap 的确切条目数。键将分别是字符串和整数。

我的问题是,设置初始容量和负载因子的最佳方法是什么?

我是否应该将容量设置为等于它将拥有的元素数量并将负载容量设置为 1.0?我希望在不使用太多内存的情况下获得绝对最佳的性能。但是,我担心该表不会以最佳方式填充。使用所需的确切大小的表,是否不会发生键冲突,导致(通常很短)扫描以找到正确的元素?

假设(这是一个延伸)哈希函数是整数键的简单模 5,这是否意味着键 5、10、15 会命中同一个桶,然后导致查找填充他们旁边的水桶?更大的初始容量会提高性能吗?

另外,如果有比哈希图更好的数据结构,我也完全愿意接受。

【问题讨论】:

  • 总条目将在 20 - 50 之间,字符串键长度的字符数将在 10-30 之间
  • 那是相当小的,你确定你还需要担心吗?除非您有很多实例,否则请使用默认的 HashMap 参数。
  • if( (Time_saved_per_operation_because_of_this_Optimization x Number_of_Operations)
  • 这将被高度利用,并且花费在此上的时间和进行更改的时间非常少。感谢所有的 cmets!
  • @instanceofTom 您忘记将knowledge gained 添加到您的公式中

标签: java hashmap


【解决方案1】:

如果您的数据没有完美的散列函数,并且假设这真的不是对真正无关紧要的事情的微优化,我会尝试以下方法:

假设 HashMap 使用的默认负载容量 (.75) 在大多数情况下是一个不错的值。在这种情况下,您可以使用它,并根据您自己对它将容纳多少项目的了解来设置 HashMap 的初始容量 - 将其设置为初始容量 x .75 = 项目数(向上取整)。

如果它是一个更大的映射,在高速查找非常关键的情况下,我建议使用某种trie 而不是哈希映射。对于长字符串,在大型地图中,您可以通过使用更面向字符串的数据结构(例如 trie)来节省空间和时间。

【讨论】:

    【解决方案2】:

    假设你的散列函数是“好”的,最好的办法是将初始大小设置为预期的元素数量,假设你可以廉价地得到一个好的估计。这样做是个好主意,因为当 HashMap 调整大小时,它必须重新计算表中每个键的哈希值。

    将负载系数保留为0.750.75 的值是根据经验选择的,作为主哈希数组的哈希查找性能和空间使用之间的良好折衷。当您提高负载因子时,平均查找时间将显着增加。

    如果您想深入研究哈希表行为的数学:Donald Knuth (1998)。计算机编程的艺术”。 3:排序和搜索(第 2 版)。艾迪生-韦斯利。第 513-558 页。国际标准书号 0-201-89685-0。

    【讨论】:

    • 我认为这个答案有问题。如果您非常关心 HashMap 的大小调整,则不应将初始容量设置为预期的元素数量(例如 100)并将加载因子设置为 0.75,因为这意味着 HashMap 将始终在某个点(例如第 75 个元素)调整大小一次。如果您将负载因子保持在 0.75 并希望防止 HashMap 调整大小,则需要将初始容量设置为 (expectedSize / 0.75) + 1
    【解决方案3】:

    我发现最好不要乱用默认设置,除非我真的需要。

    Hotspot 在优化方面做得很好。

    无论如何;我会先使用分析器(比如 Netbeans Profiler)来衡量问题。

    我们通常会存储包含 10000 个元素的映射,如果您有良好的等号和哈希码实现(字符串和整数也可以!)这将比您可能进行的任何加载更改更好。

    【讨论】:

      【解决方案4】:

      假设(这是一个延伸)哈希函数是整数键的简单 mod 5

      不是。来自 HashMap.java:

      static int hash(int h) {
        // This function ensures that hashCodes that differ only by
        // constant multiples at each bit position have a bounded
        // number of collisions (approximately 8 at default load factor).
        h ^= (h >>> 20) ^ (h >>> 12);
        return h ^ (h >>> 7) ^ (h >>> 4);
      }
      

      我什至不会假装我明白这一点,但它看起来就是为了处理这种情况而设计的。

      还要注意,桶的数量也始终是 2 的幂,无论您要求什么大小。

      【讨论】:

      • 对散列的假设只是猜测会发生冲突的事实,获得数据完美散列的机会可能是不可能的。即使有这个函数(我也不明白),我猜它很有可能不会完美地散列我传递的字符串。感谢您的回复!
      【解决方案5】:

      条目以类似随机的方式分配给存储桶。因此,即使您的存储桶与条目一样多,一些存储桶也会发生冲突。

      如果你有更多的桶,你就会有更少的碰撞。然而,更多的桶意味着在内存中分散,因此速度更慢。通常,0.7-0.8 范围内的负载因子大致是最佳的,因此可能不值得更改。

      与以往一样,在您对这些东西进行微调之前,可能值得进行分析。

      【讨论】:

      • “更多的桶意味着在内存中分散,因此更慢”。除非您在谈论纳米优化,否则我很确定这是非常不正确的。通过进行相应的哈希计算(恒定时间)来查找密钥,然后进行取模以找到存储桶,然后遍历存储桶的内容,直到请求的密钥等于()存储的密钥。所以越大越快(除了最奇怪的哈希情况外)。
      • 缓存局部性在现代系统中非常重要。如果数组过长,则更有可能导致缓存未命中。将负载因子移出对铲斗碰撞几乎没有影响。大概这种效果在 C++ 等语言中更为明显,因为所有内容(列表、哈希、键和值的第一个链接)都可以存储在数组中。
      • @TomHawtin-tackline :我不明白你的意思。如果桶的数量等于元素的数量,你说的是“在内存中展开”。如果您使用较少的存储桶,那么每个存储桶将必须容纳许多元素。有什么办法让记忆保持不变吗?
      • @Ashwin 阵列使用的内存。
      • @TomHawtin-tackline:对不起。你想说什么?
      猜你喜欢
      • 2018-11-03
      • 2013-08-27
      • 1970-01-01
      • 2023-03-03
      • 2011-10-30
      • 1970-01-01
      • 1970-01-01
      • 2017-05-21
      • 1970-01-01
      相关资源
      最近更新 更多