这是我的第 205 期分享

作者 | 王磊

来源 | Java中文社群(ID:javacn666)

转载请联系授权(微信ID:GG_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 8final Node[] resize() {// 扩容前的数组    Node[] 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)                  oldCap >= DEFAULT_INITIAL_CAPACITY)            newThr = oldThr <1; // double threshold    }// 当前数组没有数据,使用初始化的值else if (oldThr > 0) // initial capacity was placed in threshold        newCap = 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 float)MAXIMUM_CAPACITY ?                  (int)ft : Integer.MAX_VALUE);    }    threshold = newThr; @SuppressWarnings({"rawtypes","unchecked"})    Node[] newTab = (Node[])new Node[newCap];// 开始扩容,将新的容量赋值给 table    table = newTab;// 原数据不为空,将原数据复制到新 table 中if (oldTab != null) {// 根据容量循环数组,复制非空元素到新 tablefor (int j = 0; j             Node e;if ((e = oldTab[j]) != null) {                oldTab[j] = null;// 如果链表只有一个,则进行直接赋值if (e.next == null)                    newTab[e.hash & (newCap - 1)] = e;else if (e instanceof TreeNode)// 红黑树相关的操作                    ((TreeNode)e).split(this, newTab, j, oldCap);else { // preserve order// 链表复制,JDK 1.8 扩容优化部分                    Node loHead = null, loTail = null;                    Node hiHead = null, hiTail = null;                    Node next;do {                        next = e.next;// 原索引if ((e.hash & oldCap) == 0) {if (loTail == null)                                loHead = e;else                                loTail.next = e;                            loTail = e;                        }// 原索引 + oldCapelse {if (hiTail == null)                                hiHead = e;else                                hiTail.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 官方提供的 JMH(Java Microbenchmark Harness,JAVA 微基准测试套件)测试框架,首先现在 pom.xml 中添加 JMH 引用,配置如下:

<dependency>   <groupId>org.openjdk.jmhgroupId>   <artifactId>jmh-coreartifactId>   <version>{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(); // 执行测试    }

    @Benchmark    public void noSizeTest(Blackhole blackhole) {        Map map = new HashMap();        for (int i = 0; i 1024; i++) {            map.put(i, i);        }        // 为了避免 JIT 忽略未被使用的结果        blackhole.consume(map);    }

    @Benchmark    public 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创建线程池?

关注公众号发送”进群“,磊哥拉你进读者群。

2 resize 到指定大小_阿里巴巴为什么让初始化集合时必须指定大小?相关推荐

  1. 阿里巴巴为什么让初始化集合时必须指定大小?

    来源 | Java中文社群(ID:javacn666) 哈喽,亲爱的小伙伴们,技术学磊哥,进步没得说!欢迎来到新一期的性能解读系列,我是磊哥. 今天给大家带来的是关于阿里巴巴<Java开发手册& ...

  2. python按指定条件筛选_求Python筛选数字集合内满足指定条件的数据方法,python筛选,唯一匹配是指mdash...

    求Python筛选数字集合内满足指定条件的数据方法,python筛选,唯一匹配是指&mdash 唯一匹配是指----任何找出来的一对数中,位于一个集合中的数只能和另一个集合中的唯一 一个数匹配 ...

  3. php 限制图片大小代码,微信小程序在上传图片时如何限制大小(附代码)

    本篇文章给大家带来的内容是关于微信小程序在上传图片时如何限制大小(附代码),有一定的参考价值,有需要的朋友可以参考一下,希望对你有所帮助. 最近有一个微信小程序的项目,要求是上传多张图片,数量不能超过 ...

  4. linux查看图像大小_如何在Linux上调整一批图像的大小?

    linux查看图像大小 Resizing images on Linux with gThumb is easy. However, I have a batch of images inside a ...

  5. vb中怎么使图片适应框的大小_怎么让放进的图片随框大小而变

    当前位置:我的异常网» VB » 怎么让放进的图片随框大小而变 怎么让放进的图片随框大小而变 www.myexceptions.net  网友分享于:2013-01-30  浏览:6次 如何让放进的图 ...

  6. python用电度数设计_在Python中动态模拟时绘制电压大小

    我使用psspy(PSS / E,工程电力系统模拟器)来模拟电力系统. 我在公共汽车上应用了一个三相故障并且排队.现在我想绘制动态模拟过程中的电压大小,我的python代码如下: import sys ...

  7. python jupyter notebook怎么调字体大小_配置Jupyter的代码主题 字体以及字体大小 代码自动补全...

    jupyter默认没有代码提示,字体非常的难看,特别是引号,一直想更换字体和主题,就把方法整理了一下.如果你单独想更换字体的话,需要修改浏览器的字体,这个是受浏览器的影响. 如果想更换主题往下看,代码 ...

  8. aws php 上传文件 限制大小_单个文件大小 上传百度云盘 微信发送 有大小限制 怎么破?...

    问题描述 百度云盘,单个文件20G,如果一个文件太大,就不能上传成功. 微信发送文件,单个文件100Mb,如果太大,就无法发送. QQ也有类似限制 如果解决呢??? 问题解决 使用rar文件压缩,或者 ...

  9. python colorbar字体大小_如何更改colorbar上基数和指数的字体大小?

    首先,让我们拼凑一个独立的例子来演示您的问题.您已经更改了colorbar的刻度标签的大小,但是偏移标签没有更新.例如,如果颜色条顶部的文本与刻度标签的大小匹配,那就更好了:import numpy ...

最新文章

  1. HAproxy七层负载均衡——环境搭建及实现过程详解
  2. Linux运维架构师、SRE技术图谱
  3. AD20学习笔记4---网表导入及模块化布局设计
  4. RabbitMQ消息队列应用
  5. matlab如何找出最小的数据,读取数据并找出全部数据的最大值和最小值
  6. java 迭代器失效_迭代器失效的几种情况
  7. 西瓜书-感知机与BP算法
  8. web前端行业调研报告_web前端开发述职报告
  9. 使用VsCode搭建Vue开发环境
  10. 最佳实践 | 联通数科基于 DolphinScheduler 的二次开发
  11. 使用教育邮箱享正版Jetbrains 套件,IDEA正版webstrom正版
  12. html js开发课程表,利用JS实现手机移动端课程表代码
  13. wordpress添加Auto Highslide图片灯箱效果
  14. 3、基于注解的IoC装配与依赖注入
  15. 使用APICloud AVM框架开发人事档案管理助手APP
  16. 计算机测试培训经历,学习经历经验分享
  17. centos7下面php5.6添加postgresql相关扩展【试行】
  18. BSCI认证辅导,什么样的工厂可以自己向公证行申请
  19. 用Pandas分析了75w多条数据,揭秘美国选民的总统喜好
  20. VC环境下简单的贪吃蛇

热门文章

  1. link-time version is older than compile-time version
  2. 找不到org.springframework.dao.support.DaoSupport的类文件
  3. java自学语法_Java自学笔记(一):基础知识
  4. exp oracle所有数据库命令,oracle数据库exp命令
  5. mysql 非正常关闭,centos非正常关机后,mysql竟然启动不了
  6. (填坑:SQL打印两次)mybatisplus+p6spy 日志打印
  7. 云南林业职业技术学院计算机考试试题,云南林业职业技术学院单招模拟题(含解析)9.docx...
  8. 3dmax挤出制作窗花_给想学3dmax,又不知如何快速入手的你 新手学习3dmax的建议...
  9. C++11学习之share_ptr和weak_ptr
  10. linux设备驱动——总线、设备、驱动