网站开发运营工程师待遇,住房城乡建设部门户网站主页,做网站如何对接支付,沈阳网站建设电话这是我的第 205 期分享作者 | 王磊来源 | Java中文社群#xff08;ID#xff1a;javacn666#xff09;转载请联系授权#xff08;微信ID#xff1a;GG_Stone#xff09;哈喽#xff0c;亲爱的小伙伴们#xff0c;技术学磊哥#xff0c;进步没得说#xff01;欢迎来到… 这是我的第 205 期分享作者 | 王磊来源 | Java中文社群IDjavacn666转载请联系授权微信IDGG_Stone哈喽亲爱的小伙伴们技术学磊哥进步没得说欢迎来到新一期的性能解读系列我是磊哥。今天给大家带来的是关于阿里巴巴《Java开发手册》泰山版最新中关于集合初始化时的性能建议。阿里巴巴《Java开发手册》第 1 章编程规范第 6 节集合处理的第 17 条规定如下【推荐】集合初始化时指定集合初始值大小。说明HashMap 使用 HashMap(int initialCapacity) 初始化如果暂时无法确定集合大小那么指定默认值16即可。正例initialCapacity (需要存储的元素个数 / 负载因子) 1。注意负载因子即 loader factor默认为 0.75如果暂时无法确定初始值大小请设置为 16即默认值。反例HashMap 需要放置 1024 个元素由于没有设置容量初始大小随着元素不断增加容量 7 次被迫扩大resize 需要重建 hash 表。当放置的集合元素个数达千万级别时不断扩容会严重影响性能。规范解读此规范的主要目的完全是出于性能考虑查看 HashMap 的源码也就可以发现此规范的原因如果我们能为集合设置合理的大小就可以避免 HashMap 的扩容操作而 HashMap 的扩容方法 resize 有很多逻辑判断和业务操作如果设置了合理的大小就可以避免执行更多的代码因此就可以更大限度的提高集合的执行效率HashMap 的 resize 源码如下// 源码基于 JDK 8
final NodeK,V[] resize() {// 扩容前的数组NodeK,V[] oldTab table;// 扩容前的数组的大小和阈值int oldCap (oldTab null) ? 0 : oldTab.length;int oldThr threshold;// 预定义新数组的大小和阈值int newCap, newThr 0;if (oldCap 0) {// 超过最大值就不再扩容了if (oldCap MAXIMUM_CAPACITY) {threshold Integer.MAX_VALUE;return oldTab;}// 扩大容量为当前容量的两倍但不能超过 MAXIMUM_CAPACITYelse if ((newCap oldCap 1) MAXIMUM_CAPACITY oldCap DEFAULT_INITIAL_CAPACITY)newThr oldThr 1; // double threshold}// 当前数组没有数据使用初始化的值else if (oldThr 0) // initial capacity was placed in thresholdnewCap oldThr;else { // zero initial threshold signifies using defaults// 如果初始化的值为 0则使用默认的初始化容量newCap DEFAULT_INITIAL_CAPACITY;newThr (int)(DEFAULT_LOAD_FACTOR * DEFAULT_INITIAL_CAPACITY);}// 如果新的容量等于 0if (newThr 0) {float ft (float)newCap * loadFactor;newThr (newCap MAXIMUM_CAPACITY ft (float)MAXIMUM_CAPACITY ?(int)ft : Integer.MAX_VALUE);}threshold newThr; SuppressWarnings({rawtypes,unchecked})NodeK,V[] newTab (NodeK,V[])new Node[newCap];// 开始扩容将新的容量赋值给 tabletable newTab;// 原数据不为空将原数据复制到新 table 中if (oldTab ! null) {// 根据容量循环数组复制非空元素到新 tablefor (int j 0; j oldCap; j) {NodeK,V e;if ((e oldTab[j]) ! null) {oldTab[j] null;// 如果链表只有一个则进行直接赋值if (e.next null)newTab[e.hash (newCap - 1)] e;else if (e instanceof TreeNode)// 红黑树相关的操作((TreeNodeK,V)e).split(this, newTab, j, oldCap);else { // preserve order// 链表复制JDK 1.8 扩容优化部分NodeK,V loHead null, loTail null;NodeK,V hiHead null, hiTail null;NodeK,V next;do {next e.next;// 原索引if ((e.hash oldCap) 0) {if (loTail null)loHead e;elseloTail.next e;loTail e;}// 原索引 oldCapelse {if (hiTail null)hiHead e;elsehiTail.next e;hiTail e;}} while ((e next) ! null);// 将原索引放到哈希桶中if (loTail ! null) {loTail.next null;newTab[j] loHead;}// 将原索引 oldCap 放到哈希桶中if (hiTail ! null) {hiTail.next null;newTab[j oldCap] hiHead;}}}}}return newTab;
}
性能评测接下来我们来测试一下设置 size 的性能和不设置 size 的性能差别我们已知需要插入 1024 个数据根据默认的负载因子 0.75 和公式 (存储元素个数/负载因子)1 得出需要设置的大小为 1367取整。小贴士公式“(存储元素个数/负载因子)1”说明因为 HashMap 的实际存储量等于元素个数*负载因子为了防止 HashMap 扩容所以公式必须是“(存储元素个数/负载因子)1”才能防止动态扩容。本文我们依旧使用 Oracle 官方提供的 JMHJava Microbenchmark HarnessJAVA 微基准测试套件测试框架首先现在 pom.xml 中添加 JMH 引用配置如下!-- https://mvnrepository.com/artifact/org.openjdk.jmh/jmh-core --
dependencygroupIdorg.openjdk.jmh/groupIdartifactIdjmh-core/artifactIdversion{version}/version
/dependency
然后编写完整的测试代码import org.openjdk.jmh.annotations.*;
import org.openjdk.jmh.infra.Blackhole;
import org.openjdk.jmh.runner.Runner;
import org.openjdk.jmh.runner.RunnerException;
import org.openjdk.jmh.runner.options.Options;
import org.openjdk.jmh.runner.options.OptionsBuilder;import java.util.HashMap;
import java.util.Map;
import java.util.concurrent.TimeUnit;BenchmarkMode(Mode.AverageTime) // 测试完成时间
OutputTimeUnit(TimeUnit.NANOSECONDS)
Warmup(iterations 2, time 1, timeUnit TimeUnit.SECONDS) // 预热 2 轮每次 1s
Measurement(iterations 5, time 3, timeUnit TimeUnit.SECONDS) // 测试 5 轮每次 3s
Fork(1) // fork 1 个线程
State(Scope.Thread) // 每个测试线程一个实例
public class AlibabaHashMapTest {public static void main(String[] args) throws RunnerException {// 启动基准测试Options opt new OptionsBuilder().include(AlibabaHashMapTest.class.getSimpleName()) // 要导入的测试类.build();new Runner(opt).run(); // 执行测试}Benchmarkpublic void noSizeTest(Blackhole blackhole) {Map map new HashMap();for (int i 0; i 1024; i) {map.put(i, i);}// 为了避免 JIT 忽略未被使用的结果blackhole.consume(map);}Benchmarkpublic void setSizeTest(Blackhole blackhole) {Map map new HashMap(1367);for (int i 0; i 1024; i) {map.put(i, i);}// 为了避免 JIT 忽略未被使用的结果blackhole.consume(map);}
}
测试结果如下从上述结果可以看出设置了大小的 HashMap 的性能约是没有设置大小的 1.29 倍。总结在初始化集合时如果已知集合的数量那么一定要在初始化时设置集合的容量大小这样就可以有效的提高集合的性能但需要注意的是 HashMap 的实际存储量是“元素个数*负载因子”而负载因子默认是 0.75因此在设置大小时要使用“(存储元素个数/负载因子)1”的公式计算出正确的值再进行设置。往期推荐
阿里新版《Java 开发手册(泰山版)》内容解读附下载地址阿里为什么禁用Executors创建线程池关注公众号发送”进群“磊哥拉你进读者群。