java|为什么HashMap初始化需要设定大小,HashMap初始化大小设定多少合适

HashMap的介绍 在开始之前,先看下在官方文档中是如何介绍HashMap的:

An instance of HashMap has two parameters that affect its performance: initial capacity and load factor. The capacity is the number of buckets in the hash table, and the initial capacity is simply the capacity at the time the hash table is created. The load factor is a measure of how full the hash table is allowed to get before its capacity is automatically increased. When the number of entries in the hash table exceeds the product of the load factor and the current capacity, the hash table is rehashed (that is, internal data structures are rebuilt) so that the hash table has approximately twice the number of buckets.
As a general rule, the default load factor (.75) offers a good tradeoff between time and space costs. Higher values decrease the space overhead but increase the lookup cost (reflected in most of the operations of the HashMap class, including get and put). The expected number of entries in the map and its load factor should be taken into account when setting its initial capacity, so as to minimize the number of rehash operations. If the initial capacity is greater than the maximum number of entries divided by the load factor, no rehash operations will ever occur.
翻译过来就是:
HashMap的实例有两个影响其性能的参数:初始容量和装载因子。容量是哈希表中的桶数,初始容量就是创建哈希表时的容量。负载因子是一种度量方法,用来衡量在自动增加哈希表的容量之前,哈希表允许达到的满度。当哈希表中的条目数超过负载因子和当前容量的乘积时,哈希表将被重新哈希(即重新构建内部数据结构),这样哈希表的桶数大约是原来的两倍。
作为一般规则,默认的负载系数(.75)在时间和空间成本之间提供了一个很好的折衷。较高的值会减少空间开销,但会增加查找成本(反映在HashMap类的大多数操作中,包括get和put)。在设置初始容量时,应该考虑映射中的预期条目数及其装载因子,以便最小化重散列操作的数量。如果初始容量大于最大条目数除以装载因子,则不会发生重新散列操作。
上面的说法总结一下就是:HashMap的扩容条件就是当HashMap中的元素个数(size)超过临界值(threshold)时就会自动扩容。在HashMap中,threshold = loadFactor * capacity。扩容时新的capacity *= 2。
代码说明初始化的好处 通过上面的说明可以看出,初始容量是影响性能的一个方面,通过代码来直观的感受下:
import java.util.HashMap; import java.util.Map; public class Test { public static void main(String[] args) { int num = 100000; //未初始化大小 Map map1 = new HashMap(); long s1 = System.currentTimeMillis(); for (int i = 0; i < num; i++) { map1.put(i, i); } long e1 = System.currentTimeMillis(); System.out.println("未初始化大小:"+ (e1 - s1)); //初始化一半大小 Map map2 = new HashMap(num/2); long s2 = System.currentTimeMillis(); for (int i = 0; i < num; i++) { map2.put(i, i); } long e2 = System.currentTimeMillis(); System.out.println("初始化一半大小:"+ (e2 - s2)); //初始化一样大小 Map map3 = new HashMap(num); long s3 = System.currentTimeMillis(); for (int i = 0; i < num; i++) { map3.put(i, i); } long e3 = System.currentTimeMillis(); System.out.println("初始化一样大小:"+ (e3 - s3)); } }

得到的结果:
未初始化大小:16 初始化一半大小:12 初始化一样大小:8

可以看出,HashMap初始化时合理的大小设置是能够提升性能的。但这样就可以了吗?为什么说扩容因子也是影响性能的一个方面。
HashMap中的负载(扩容)因子 当我们使用HashMap(int initialCapacity)来初始化容量的时候,HashMap并不会使用我们传进来的initialCapacity直接作为初识容量,JDK会默认帮我们计算一个相对合理的值当做初始容量。所谓合理值,其实是找到第一个比用户传入的值大的2的幂。如用户传入的是7,第一个比7大的2的幂是2的3次方8,所以初始化容量就为8。
如果在新建HashMap时,我们已知容量为7个,传入8就可以了吗?这个值看似合理,实际上并不尽然。因为HashMap在根据用户传入的capacity计算得到的默认容量,并没有考虑到loadFactor这个因素,只是简单机械的计算出第一个大约这个数字的2的幂。
loadFactor是负载因子,当HashMap中的元素个数(size)超过 threshold = loadFactor * capacity时,就会进行扩容。

也就是说,如果我们设置的默认值是7,经过JDK处理之后,HashMap的容量会被设置成8,但是,这个HashMap在元素个数达到 8*0.75 = 6的时候就会进行一次扩容,这明显是我们不希望见到的。
那么,到底设置成什么值比较合理呢?
这个值的计算方法就是:
return (int) ((float) expectedSize / 0.75F + 1.0F);
代码验证设定的大小是否合适
public class Test { public static void main(String[] args) { int num = 100000; //未初始化大小 Map map1 = new HashMap(); long s1 = System.currentTimeMillis(); for (int i = 0; i < num; i++) { map1.put(i, i); } long e1 = System.currentTimeMillis(); System.out.println("未初始化大小:"+ (e1 - s1)); //初始化一半大小 Map map2 = new HashMap(num/2); long s2 = System.currentTimeMillis(); for (int i = 0; i < num; i++) { map2.put(i, i); } long e2 = System.currentTimeMillis(); System.out.println("初始化一半大小:"+ (e2 - s2)); //初始化一样大小 Map map3 = new HashMap(num); long s3 = System.currentTimeMillis(); for (int i = 0; i < num; i++) { map3.put(i, i); } long e3 = System.currentTimeMillis(); System.out.println("初始化一样大小:"+ (e3 - s3)); //初始化大小考虑到扩容因子 Map map4 = new HashMap((int)(num/0.75+1.0)); long s4 = System.currentTimeMillis(); for (int i = 0; i < num; i++) { map4.put(i, i); } long e4 = System.currentTimeMillis(); System.out.println("初始化大小考虑到扩容因子:"+ (e4 - s4)); } }

结果
未初始化大小:16 初始化一半大小:12 初始化一样大小:8 初始化大小考虑到扩容因子:4

有些时候,我们到底要不要设置HashMap的初识值,这个值又设置成多少,真的有那么大影响吗?其实也不见得!
可是,大的性能优化,不就是一个一个的优化细节堆叠出来的吗?
再不济,以后你写代码的时候,也可以让同事和老板眼前一亮。
【java|为什么HashMap初始化需要设定大小,HashMap初始化大小设定多少合适】或者哪一天你碰到一个面试官问你一些细节的时候,你也能有个印象,或者某一天你也可以拿这个出去面试问其他人

    推荐阅读