索引List时的最佳HashMap初始容量

时间:2013-04-05 21:38:21

标签: java algorithm dictionary hashmap

我有一个列表(List<T> list),我想使用地图(HashMap<Integer, T> map)通过其ID对其对象编制索引。我总是在list.size()构造函数中使用HashMap作为初始容量,如下面的代码所示。这是在这种情况下使用的最佳初始容量吗?

注意:我永远不会在地图上添加更多项目。

List<T> list = myList;
Map<Integer, T> map = new HashMap<Integer, T>(list.size());
for(T item : list) {
    map.put(item.getId(), item);
}

6 个答案:

答案 0 :(得分:26)

如果您希望避免重复HashMap,并且您知道其他元素不会放入HashMap,那么您必须考虑到加载因子以及初始容量。载荷系数for a HashMap defaults to 0.75

每当添加新条目时,确定是否需要重新散列的计算,例如, put放置一个新的键/值。因此,如果您指定的初始容量为list.size(),且加载因子为1,那么它将在最后一个put后重新散列。因此,为防止重新散列,请使用加载因子1和容量list.size() + 1

修改

查看HashMap源代码,如果大小达到或超过阈值,它将重新进行,因此它不会在最后put上重新进行。所以看起来list.size()的容量应该没问题。

HashMap<Integer, T> map = new HashMap<Integer, T>(list.size(), 1.0);

以下是HashMap源代码的相关部分:

void addEntry(int hash, K key, V value, int bucketIndex) {
    Entry<K,V> e = table[bucketIndex];
    table[bucketIndex] = new Entry<>(hash, key, value, e);
    if (size++ >= threshold)
        resize(2 * table.length);
}

答案 1 :(得分:13)

'capacity'关键字的定义不正确,并且没有按照通常预期的方式使用。

默认情况下,HashMap的“加载因子”为0.75,这意味着当HashMap中的条目数达到所提供容量的75%时,它将调整数组的大小并重新散列。

例如,如果我这样做:

Map<Integer, Integer> map = new HashMap<>(100);

当我添加第75个条目时,地图会将Entry表的大小调整为2 * map.size()(或2 * table.length)。所以我们可以做一些事情:

  1. 更改负载系数 - 这可能会影响地图的效果
  2. 将初始容量设置为list.size()/ 0.75 + 1
  3. 最好的选择是两者中的后者,让我解释一下这里发生了什么:

    list.size() / 0.75
    

    这将返回list.size()+ list.size()的25%,例如,如果我的列表的大小为100,它将返回133.然后,如果大小调整了地图的大小,我们会添加1它等于初始容量的75%,所以如果我们有一个大小为100的列表,我们将初始容量设置为134,这意味着从列表中添加所有100个条目不会产生任何大小调整地图。

    最终结果:

    Map<Integer, Integer> map = new HashMap<>(list.size() / 0.75 + 1);
    

答案 2 :(得分:12)

你做得很好。通过这种方式,您可以确保哈希映射的至少足够容纳初始值。如果您有关于哈希映射的使用模式的更多信息(例如:它是否经常更新?是否经常添加许多新元素?),您可能希望设置更大的初始容量(例如,list.size() * 2),但永远不会降低使用分析器确定初始容量是否过早失效。

<强>更新

感谢@PaulBellora建议初始容量应设置为(int)Math.ceil(list.size() / loadFactor)(通常,默认加载因子为0.75),以避免初始调整大小。

答案 3 :(得分:12)

Guava的Maps.newHashMapWithExpectedSize使用此辅助方法根据预期的某些值计算默认加载因子0.75的初始容量:

/**
 * Returns a capacity that is sufficient to keep the map from being resized as
 * long as it grows no larger than expectedSize and the load factor is >= its
 * default (0.75).
 */
static int capacity(int expectedSize) {
    if (expectedSize < 3) {
        checkArgument(expectedSize >= 0);
        return expectedSize + 1;
    }
    if (expectedSize < Ints.MAX_POWER_OF_TWO) {
        return expectedSize + expectedSize / 3;
    }
    return Integer.MAX_VALUE; // any large value
}

参考:source

来自newHashMapWithExpectedSize文档:

  

创建一个HashMap实例,具有足够高的“初始容量”   应该持有expectedSize元素而不增长。这种行为   不能得到广泛保证,但它被认为是真实的   OpenJDK 1.6。也不能保证该方法不是   无意中超大返回的地图。

答案 4 :(得分:4)

根据reference documentation of java.util.HashMap

  

在设置初始容量时,应考虑地图中预期的条目数及其加载因子,以便最大限度地减少重新扫描操作的次数。如果初始容量大于最大条目数除以负载因子,则不会发生任何重新连接操作。

这意味着,如果您事先知道HashMap应存储多少条目,则可以通过选择适当的初始容量和加载因子来阻止重新散列。但是:

  

作为一般规则,默认负载系数(.75)在时间和空间成本之间提供了良好的权衡。较高的值会减少空间开销,但会增加查找成本(反映在HashMap类的大多数操作中,包括get和put)。

答案 5 :(得分:0)

经验法则,如果您不知道负载系数/容量内部要素:

initialCapacityToUse = (Expected No. of elements in map / 0.75) + 1

使用此初始容量值,不会在给定预期编号的情况下进行存储的重新哈希处理。地图中的元素。