🎁 前言
最近,在业务开发过程中,遇到了个场景:需要对数量约1w的pojo对象转HashSet,并且因为tps较高,需要对性能做优化。
大概的业务代码大致如下:
有两个和性能相关的点:
HashSet,或者说HashMap在1w数据量情况下,初始化容量性能有多少差距?
使用stream流toSet与for循环add元素之间性能有多少差距?
🥠 环境
JMH version: 1.36
VM version: JDK 1.8.0_352, OpenJDK 64-Bit Server VM, 25.352-b08
操作系统为Windows 10企业版
🥯 性能测试
考虑“HashSet,或者说HashMap在1w数据量情况下,初始化容量性能有多少差距?”问题时候,在网上查相关测试时,发现了《Java中使用HashMap时指定初始化容量性能一定会更好吗?》 文章里作者的测试结果居然和我的认知常识有点出入。

有些情况下指定容量的性能还比不指定容量时差!!
抱着怀疑的态度,我打算在自己本地跑一下测试,看是不是真是“大家”错了。
这里对原作者的测试做了些调整:加了param参数,避免自己改参数做表格;将需要添加到HashMap的数据提前初始化好,减少random.nextInt()对结果的干扰。
结果发现还真是和原文作者跑出来的差不多,但差距却没有原作者跑出来的那么大。
难道阿里Java规范是拍脑袋写出来的?
让我们再看一眼阿里Java规范关于集合初始化的建议是咋写的。
【推荐】集合初始化时,指定集合初始值大小。说明:HashMap使用HashMap(int initialCapacity) 初始化,正例:initialCapacity = (需要存储的元素个数 / 负载因子) + 1。注意负载因子(即loader factor)默认为0.75,如果暂时无法确定初始值大小,请设置为16(即默认值)。反例:HashMap需要放置1024个元素,由于没有设置容量初始大小,随着元素不断增加,容量7次被迫扩大,resize需要重建hash表,严重影响性能。
原来根本不是要按照需要存储的元素个数初始化HashMap的大小,那我们重新再跑下测试,是不是真的有差距呢?
从结果来看,正确地初始化HashMap的容量大小对性能影响很大。
确认了正确初始化HashMap容量大小的性能差距后,那使用stream流toSet与for循环add元素之间性能有多少差距?
for循环也有多种写法,比如原始写法,冒号写法,foreach写法,不同的写法之间是否对性能会造成比较大的影响?
测试的代码如下:
测试结果如下。从结果来看,在1w左右数据量,for循环的各个写法之间差距不大,冒号写法相对低了点。使用stream流最后Collectors.toSet性能最差,应该和没有初始化容量大小有关系。
🌭 源码分析
1、Collectors.toSet直接new了个没有初始化容量的HashSet,在add元素过程中会触发多次resize。
2、在HashMap的put元素过程中,会判断当前map的元素个数,当达到当前容量的
loadFactor 负载因子时(默认时0.75),会进行扩容。这也是为什么HashMap初始化容量时,建议按(需要存储的元素个数 / 负载因子) + 1来初始化。📎 参考资料
jmh
openjdk • Updated Dec 9, 2023
- 作者:Yibin
- 链接:https://yibin.dev/article/%E6%80%A7%E8%83%BD%E6%B5%8B%E8%AF%95
- 声明:本文采用 CC BY-NC-SA 4.0 许可协议,转载请注明出处。
相关文章








