【问题标题】:Is it better to specify HashMap's initial capacity when it is not a power of 2 than to not specify at all?当 HashMap 的初始容量不是 2 的幂时指定它是否比不指定更好?
【发布时间】:2014-08-14 18:07:43
【问题描述】:

假设我知道 HashMap 中的键值对的确切数量,并且我知道它不是 2 的幂。在这些情况下,我应该指定初始容量吗?我可以得到 2 的最接近的幂并指定它,但我仍然想知道在这种情况下(当我不想计算 2 的最接近的幂时)哪个会更好。

谢谢!

【问题讨论】:

  • 如果内存够用,无论如何都会四舍五入到最接近的 2 次方,所以请继续指定初始容量。
  • @user3580294 我查看了源代码,但不是。仅当您使用构造函数 mit 另一个 Map 作为参数时才会发生这种情况。
  • 肯定initialCapacity 构造函数的源代码中看到它,在 tableSizeFor() 方法下。查看 Oracle 1.8.0_05 源代码。
  • @user3580294 在 Oracle 1.7.0_51 中的 java.util.HashMap 中没有这样的东西。现在交叉检查 1.8 是否有兴趣 ;-)
  • @user3580294 看到 Mikulskis 的回答我看到它在第一次放置期间完成。但仍然不在 1.7 的构造函数中

标签: java hashmap java-6


【解决方案1】:

如果你查看java.util.HashMap源代码(java 1.7)(你可以在JDK目录下的src.zip文件中找到)你会看到HashMap的put方法使用inflateTable方法创建一个存储 HashMap 条目的数组,该方法总是将 HashMap 的容量增加到大于(或等于)您指定的大小的 2 次方。

方法如下:

    private void inflateTable(int toSize) {
        // Find a power of 2 >= toSize
        int capacity = roundUpToPowerOf2(toSize);

        threshold = (int) Math.min(capacity * loadFactor, MAXIMUM_CAPACITY + 1);
        table = new Entry[capacity];
        initHashSeedAsNeeded(capacity);
    }

因此,如果您指定的大小是 2 的幂,则无关紧要。

【讨论】:

  • 我使用的是 Java 1.6。在源代码中找不到这个方法。
【解决方案2】:

您应该将初始容量视为对HashMap 的大致预期数据的提示。通过提供正确的初始容量,您可以最大程度地减少必须重新构建地图以进行扩展的次数。例如,如果您知道您计划插入一百万条记录,则通过构建具有 1,000,000 初始容量的映射,它将确保在构建时分配足够的内存来处理这么多的插入。在那之后,在map.put() 调用期间,将来插入地图可能需要大的 O(n) 操作才能调整大小。

将此初始容量视为提示,而不是您期望 HashMap 遵循的指令,可能会帮助您了解您所描述的优化是不必要的。 HashMap 旨在在所有正常情况下都表现良好,因此虽然提供初始容量可能会有所帮助,但它通常不会对您的代码产生巨大影响,除非您正在构建 许多 新的大型地图每时每刻。在这种情况下,指定容量可以避免中间表调整大小,仅此而已。

As documented,如果您指定的初始容量太大,您可能会引入一些不必要的减速:

对集合视图的迭代需要的时间与HashMap 实例的“容量”成正比

但实际上,分配如此大的映射所浪费的内存可能会比迭代速度稍慢的迭代速度更快地给您带来问题。

请务必阅读Why does HashMap require that the initial capacity be a power of two?


您可能会考虑的一件事是切换到GuavaImmutableMap 实现;如果您事先知道地图的内容,并且您不希望更改它们,那么不可变集合将被证明更易于使用和use less memory than their mutable counterparts


以下是我使用 Scala 的 REPL(和一些个人实用功能)进行的一些快速检查,以检查 HashMap (Java 1.7) 内部发生的情况:

// Initialize with capacity=7
scala> new HashMap[String,String](7)
res0: java.util.HashMap[String,String] = {}

scala> getPrivate(res0, "table").length
res1: Int = 8

scala> ... put 7 values

// Still internally using the same array
scala> getPrivate(res0, "table").length
res9: Int = 8

// Specifying capacity 9 allocates a 16-lenth array
scala> getPrivate(new HashMap[String,String](9), "table").length
res10: Int = 16

// Copying our first map into a new map interestingly
// also allocates the default 16 slots, rather than 8
scala> getPrivate(new HashMap[String,String](res0), "table").length
res11: Int = 16

scala> ... put 10 more values in our map

scala> getPrivate(res0,"table").length
res22: Int = 32

// Copying again immediately jumps to 32 capacity
scala> getPrivate(new HashMap[String,String](res0),"table").length
res23: Int = 32

【讨论】:

  • 如果可以的话,我会尝试在 Java 1.6 上重新运行这些 Scala 示例。同时,为什么不升级,the future is here :)
  • 感谢您的回答。如果它在我手中,我会升级:)
猜你喜欢
  • 2012-01-11
  • 2015-01-30
  • 1970-01-01
  • 1970-01-01
  • 2014-12-24
  • 2011-06-22
  • 1970-01-01
  • 1970-01-01
相关资源
最近更新 更多