最近在开发过程中,通过《阿里巴巴java开发手册》规约监测代码规范,发现其建议我们在创建HashMap对象时设置HashMap的初始化容量,为此内心小小的惊了那么一下,以往是创建了多少不规范代码哇!那么,为什么要这么建议呢?为了避免之后再次出现类似问题,对HashMap做了进一步了解。
1、HashMap两个影响性能的参数
HashMap有两个参数会影响其性能,初始容量和加载因子:容量是HashMap在创建时“桶”的数量,而初始容量是哈希表在创建时分配的空间大小。加载因子是哈希表在其容量自动增加时能达到多满的衡量尺度(比如默认为0.75,即桶中数据达到3/4就不能再放数据了)。如果加载因子过大,迭代性能会下降,虽然空间开销减少。如果初始容量小于最大条目数除以加载因子,则会发生 rehash 操作。rehash操作即重建内部数据结构,一般是增加桶数为原来的两倍,rehash过程中会重新计算每个元素在数组中的位置,而这是一个非常消耗性能的操作,所以如果我们已经预知HashMap中元素的个数,那么预设元素的个数能够有效的提高HashMap的性能
2、HashMap中初始容量的合理值
HashMap的加载因子在目前JDK版本中默认为0.75,当我们使用HashMap(int initialCapacity)来初始化容量的时候,JDK会默认帮我们计算一个相对合理的值当做初始容量。那么,我们该如何定义initialCapacity的值才能避免HashMap重复扩容,从而使得性能最优?
关于这个值的设置,在《阿里巴巴Java开发手册》有以下建议:
虽然,当我们使用HashMap(int initialCapacity)来初始化容量的时候,jdk会默认帮我们计算一个相对合理的值当做初始容量。但是这个值并没有参考loadFactor的值。也就是说,如果我们设置的默认值是7,经过JDK处理之后,会被设置成8,但是,这个HashMap在元素个数达到 8*0.75 = 6的时候就会进行一次扩容,这明显是我们不希望见到的。如果我们通过expectedSize / 0.75F + 1.0F计算,7/0.75 + 1 = 10 ,10经过JDK处理之后,会被设置成16,这就大大的减少了扩容的几率。扩容的过程是比较耗费时间的,所以初始化容量要设置成expectedSize/0.75 + 1的话,可以有效的减少冲突也可以减小误差。当然,把默认容量设置成expectedSize / 0.75F + 1.0F 是一个在性能上相对好的选择,但是,同时也会牺牲些内存。
3、验证
通过java代码一个简单的实验,检验在不指定初始化容量和指定初始化容量的情况下性能情况,结果如下,从结果中,我们大体可以知道,在已知HashMap中将要存放的KV个数的时候,设置一个合理的初始化容量可以有效的提高性能。