HashMap使用HashMap(int initialCapacity)对集合进行初始化。
在默认的情况下,HashMap的容量是16。但是如果用户通过构造函数指定了一个数字作为容量,那么Hash会选择大于该数字的第一个2的幂作为容量。比如如果指定了3,则容量是4;如果指定了7,则容量是8;如果指定了9,则容量是16。
为什么要设置HashMap的初始化容量
在《阿里巴巴Java开发手册》中,有一条开发建议是建议我们设置HashMap的初始化容量。
下面我们通过具体的代码来了解下为什么会这么建议。
我们先来写一段代码在JDK1.7的环境下运行,来分别测试下,在不指定初始化容量和指定初始化容量的情况下性能情况的不同。
public static void main(String[] args) { int aHundredMillion = 10000000; // 未初始化容量 Map<Integer, Integer> map = new HashMap<>(); long s1 = System.currentTimeMillis(); for (int i = 0; i < aHundredMillion; i++) { map.put(i, i); } long s2 = System.currentTimeMillis(); System.out.println("未初始化容量,耗时: " + (s2 - s1)); // 14322 // 初始化容量为50000000 Map<Integer, Integer> map1 = new HashMap<>(aHundredMillion / 2); long s3 = System.currentTimeMillis(); for (int i = 0; i < aHundredMillion; i++) { map1.put(i, i); } long s4 = System.currentTimeMillis(); System.out.println("初始化容量5000000,耗时: " + (s4 - s3)); // 11819 // 初始化容量为100000000 Map<Integer, Integer> map2 = new HashMap<>(aHundredMillion); long s5 = System.currentTimeMillis(); for (int i = 0; i < aHundredMillion; i++) { map2.put(i, i); } long s6 = System.currentTimeMillis(); System.out.println("初始化容量为10000000,耗时: " + (s6 - s5)); // 7978 }
从以上的代码不难理解,我们创建了3个HashMap,分别使用默认的容量(16)、使用元素个数的一半(5千万)作为初始容量和使用元素个数(一亿)作为初始容量进行初始化,然后分别向其中put一亿个KV。
从上面的打印结果中可以得到一个初步的结论:在已知HashMap中将要存放的KV个数的时候,设置一个合理的初始化容量可以有效地提高性能。下面我们来简单分析一下原因。
我们知道,HashMap是有扩容机制的。所谓的扩容机制,指的是当达到扩容条件的时候,HashMap就会自动进行扩容。而HashMap的扩容条件就是当HashMap中的元素个数(Size)超过临界值(Threshold)的情况下就会自动扩容。
threshold = loadFactor * capacity
在元素个数超过临界值的情况下,随着元素的不断增加,HashMap就会发生扩容,而HashMap中的扩容机制决定了每次扩容都需要重建hash表,这一操作需要消耗大量资源,是非常影响性能的。因此,如果我们没有设置初始的容量大小,HashMap就可能会不断发生扩容,也就使得程序的性能降低了。
另外,在上面的代码中我们会发现,同样是设置了初始化容量,设置的数值不同也会影响性能,那么当我们已知HashMap中即将存放的KV个数的时候,容量的设置就成了一个问题。
HashMap中容量的初始化
开头提到,在默认的情况下,当我们设置HashMap的初始化容量时,实际上HashMap会采用第一个大于该数值的2的幂作为初始化容量。
Map<String, String> map = new HashMap<>(1); map.put("huangq", "yanggb"); Class<?> mapType = map.getClass(); Method capacity = mapType.getDeclaredMethod("capacity"); capacity.setAccessible(true); System.out.println("capacity : " + capacity.invoke(map)); // 2