2017-08-07 43 views
-2

这段代码为什么抛出异常?为什么这个hashmap初始容量试图调整大小?

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); 
+0

但是你告诉它有一个'MAX_VALUE的initialCapacity'看到[javadoc文档(https://docs.oracle.com/javase/7:当我和-Xmx9G运行它成功地与输出完毕/文档/ API/JAVA/UTIL/HashMap中。html#HashMap(int)) –

+0

是的,但初始容量意味着用于索引键的数组大小(散列) – fredcrs

回答

3
new HashMap<>(Integer.MAX_VALUE); 

您所要求的2个 -1元素,或2147483647初始阵列大小。每个元素8个字节(一个引用是64位),大约16 GB的内存。

除非您有18GB左右的堆可用,否则您将始终得到OOM错误。

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

+0

好吧,我的猜测是当我创建hashmap对象时,它不分配数组.. 。只有当每次添加第一个元素 – fredcrs

+1

时,一个元素被“put”时需要检查容量并且可能增加,所以我猜测作者解码后不会分配内存 –

+2

@fredcrs - 'HashMap'用于分配数组最初,但现在它被分配在第一次插入,这是一个非常合理和强大的优化,当你有很多空地图。 – BeeOnRope

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 ,因此实际请求的尺寸(存储在this.threshold中的为described here)取决于MAXIMUM_CAPACITY

然后,当你真的去分配数组时,实现会尝试创建一个所需大小的数组。最终,内心深处HashMap.resize()有一些检测你已经达到“最大容量”逻辑(因为你要求的最大容量的初始大小开始与),并且将底层阵列来Integer.MAX_VALUE大小:

if (newThr == 0) { 
     float ft = (float)newCap * loadFactor; 
     newThr = (newCap < MAXIMUM_CAPACITY && ft < (float)MAXIMUM_CAPACITY ? 
        (int)ft : Integer.MAX_VALUE); 
    } 

然后随后分配一个数组,其中需要至少8G的堆空间。这就是为什么你有OOME。

map size: 0 
map size: 1 
+1

在j8中ArrayList的分配也变得懒惰;比较https://stackoverflow.com/questions/34250207/in-java-8-why-is-the-default-capacity-of-arraylist-now-zero –

相关问题