为什么这个具有初始容量的hashmap试图调整大小?

时间:2017-08-07 02:18:52

标签: java dictionary hashmap initialization hashtable

为什么这段代码会抛出异常?

public static void main(String[] args) {
    Map<Integer, Integer> map = new HashMap<>(Integer.MAX_VALUE);
    System.out.println("map size: "+map.size());
    map.put(1, 1);
    System.out.println("map size: "+map.size());
}

输出:

map size: 0
Exception in thread "main" java.lang.OutOfMemoryError: Java heap space
    at java.util.HashMap.resize(HashMap.java:703)
    at java.util.HashMap.putVal(HashMap.java:628)
    at java.util.HashMap.put(HashMap.java:611)
    at com.fredcrs.codejam.NumberToBinary.main(NumberToBinary.java:24)

当hashmap填满时,它只能调整为更大的一个吗?

编辑: 使用以下命令初始化它时也会抛出相同的异常:

Map<Integer, Integer> map = new HashMap<>(Integer.MAX_VALUE-3);

2 个答案:

答案 0 :(得分:3)

new HashMap<>(Integer.MAX_VALUE);

您要求的初始数组大小为2 31 -1个元素,或2,147,483,647。每个元素8个字节(参考是64位),大约16 GB的内存。

除非你的堆有18GB左右,否则你总会收到OOM错误。

你要求16GB的数组内存,除非内存可用,否则它将失败。是否在实例化或第一次插入时失败是实现细节。在过去的某个时刻,它会在实例化时失败。最近,代码被更改为等到第一次插入。这种改变是可能的,因为分配数组的细节不是任何外部合同的一部分 - 即JavaDoc中没有提到它。

答案 1 :(得分:1)

在Oracle Java 8 JDK中,在添加元素之前,不会分配HashMap的存储空间。

如果有疑问,只需检查实现 - 您甚至可以在调试器中单步执行它。

现代JDK HashMap实现在插入第一个元素之前实际上不会分配底层数组,即使您指定了显式大小。例如,在我的JDK 8版本中,构造函数代码如下:

public HashMap(int initialCapacity, float loadFactor) {
    if (initialCapacity < 0)
        throw new IllegalArgumentException("Illegal initial capacity: " +
                                           initialCapacity);
    if (initialCapacity > MAXIMUM_CAPACITY)
        initialCapacity = MAXIMUM_CAPACITY;
    if (loadFactor <= 0 || Float.isNaN(loadFactor))
        throw new IllegalArgumentException("Illegal load factor: " +
                                           loadFactor);
    this.loadFactor = loadFactor;
    this.threshold = tableSizeFor(initialCapacity);
}

请注意,没有分配数组。此外,您请求的大小大于我的系统上的MAXIMUM_CAPACITY,即2 30 ,因此实际请求的大小(this.threshold中存储为described here })被限制在MAXIMUM_CAPACITY

然后,当您实际分配数组时,实现会尝试创建所请求大小的数组。最终,在HashMap.resize()深处有一些逻辑可以检测到你已经达到“最大容量”(因为你要求开始时最大容量的初始大小),并将底层数组的大小设置为{{ 1}}:

Integer.MAX_VALUE

然后随后分配一个2 31 -1 if (newThr == 0) { float ft = (float)newCap * loadFactor; newThr = (newCap < MAXIMUM_CAPACITY && ft < (float)MAXIMUM_CAPACITY ? (int)ft : Integer.MAX_VALUE); } 元素的数组,这需要至少8G的堆空间。这就是你得到OOME的原因。当我使用int运行时,它已成功完成输出:

-Xmx9G