为什么库不能正确处理HashMap的初始容量?

时间:2012-11-11 09:03:00

标签: java hashmap capacity

要为N个元素创建HashMap / HashSet,我们通常会new HashMap((int)(N/0.75F)+1)这很烦人。

为什么图书馆首先没有处理这个问题,并且允许像new HashMap(N)这样的初始化(不应该重新划分直到N个元素)来处理这个计算(int)(N/0.75F)+1

3 个答案:

答案 0 :(得分:2)

更新

更新以反映已更改的问题。不,没有这样的标准API,但似乎Maps.newHashMapWithExpectedSize(int)中有一个方法

  

创建一个HashMap实例,具有足够高的“初始容量”,应该持有expectedSize元素而不会增长。


  

我必须将其初始化为(int)(N / 0.75F)+1

不,不。如果您从其他HashMap创建新的Map,则HashMap默认情况下首先计算容量:

public HashMap(Map<? extends K, ? extends V> m) {
    this(Math.max((int) (m.size() / DEFAULT_LOAD_FACTOR) + 1,
                  DEFAULT_INITIAL_CAPACITY), DEFAULT_LOAD_FACTOR);
    putAllForCreate(m);
}

如果逐个添加元素,也会发生相同的过程:

void addEntry(int hash, K key, V value, int bucketIndex) {
    if ((size >= threshold) && (null != table[bucketIndex])) {
        resize(2 * table.length);
        //...
    }

    createEntry(hash, key, value, bucketIndex);
}

使用HashMap(int initialCapacity, float loadFactor)构造函数的唯一原因是,从一开始就知道要在HashMap中存储多少元素,从而避免以后调整大小和重新散列(映射具有正确的大小)一开始)。

一个有趣的实现细节是初始容量被调整到最接近的2的幂(参见:Why ArrayList grows at a rate of 1.5, but for Hashmap it's 2?):

// Find a power of 2 >= initialCapacity
int capacity = 1;
while (capacity < initialCapacity)
    capacity <<= 1;

因此,如果您希望HashMap具有定义的精确容量,请使用2的幂。

选择不同的loadFactor可以让您换取空间以获得性能 - 较小的值意味着更多的内存,但更少的冲突。

答案 1 :(得分:1)

我已经运行了以下程序

public static void main(String... args) throws IllegalAccessException, NoSuchFieldException {
    for (int i = 12; i < 80; i++) {
        Map<Integer, Integer> map = new HashMap<Integer, Integer>((int) Math.ceil(i / 0.75));
        int beforeAdding = Array.getLength(getField(map, "table"));
        for (int j = 0; j < i; j++) map.put(j, j);
        int afterAdding = Array.getLength(getField(map, "table"));
        map.put(i, i);
        int oneMore = Array.getLength(getField(map, "table"));
        System.out.printf("%,d: initial %,d, after N %,d, after N+1 %,d%n ",
                i, beforeAdding, afterAdding, oneMore);
    }
}

private static <T> T getField(Map<Integer, Integer> map, String fieldName) throws NoSuchFieldException, IllegalAccessException {
    Field table = map.getClass().getDeclaredField(fieldName);
    table.setAccessible(true);
    return (T) table.get(map);
}

打印出来

 12: initial 16, after N 16, after N+1 32
 13: initial 32, after N 32, after N+1 32
 .. deleted ..
 24: initial 32, after N 32, after N+1 64
 25: initial 64, after N 64, after N+1 64
 .. deleted ..
 47: initial 64, after N 64, after N+1 64
 48: initial 64, after N 64, after N+1 128
 49: initial 128, after N 128, after N+1 128
 .. deleted ..
 79: initial 128, after N 128, after N+1 128

这表明默认初始化器的初始容量四舍五入到下一个2的幂。此值的问题在于,如果您希望这是最终大小,则必须考虑加载因子(如果您想避免调整大小)。理想情况下,你不应该像Map复制构造函数那样为你做。

答案 2 :(得分:0)

大多数实现会在您添加更多元素时自动增长。当容器变得更充分时,大多数实现的性能也趋于降低。这就是为什么首先存在一个负载因素:留下一些空的空间。