收藏 分销(赏)

Java开发手册(嵩山版)灵魂15问.pdf

上传人:Stan****Shan 文档编号:1240246 上传时间:2024-04-19 格式:PDF 页数:122 大小:15.85MB
下载 相关 举报
Java开发手册(嵩山版)灵魂15问.pdf_第1页
第1页 / 共122页
Java开发手册(嵩山版)灵魂15问.pdf_第2页
第2页 / 共122页
Java开发手册(嵩山版)灵魂15问.pdf_第3页
第3页 / 共122页
Java开发手册(嵩山版)灵魂15问.pdf_第4页
第4页 / 共122页
Java开发手册(嵩山版)灵魂15问.pdf_第5页
第5页 / 共122页
点击查看更多>>
资源描述

1、为什么禁止使用 Apache Beanutils 进行属性的 copy?4为什么要求日期格式化时必须有使用 y 表示年,而不能用 Y?11Java 开发手册-泰山版提到的三目运算符的空指针问题到底是个怎么回事?16为什么建议初始化 HashMap 的容量大小?21Java 开发手册建议创建 HashMap 时设置初始化容量,但是多少合适呢?33为什么禁止使用 Executors 创建线程池?37为什么要求谨慎使用 ArrayList 中的 subList 方法?43为什么不建议在 for 循环中使用“+”进行字符串拼接?50为什么禁止在 foreach 循环里进行元素的 remove/add

2、操作?60为什么禁止工程师直接使用日志系统(Log4j、Logback)中的 API?72为什么禁止把 SimpleDateFormat 定义成 static 变量?80为什么禁止开发人员使用 isSuccess 作为变量名?91为什么禁止开发人员修改 serialVersionUID 字段的值?103为什么建议开发者谨慎使用继承?115为什么禁止使用 count(列名)或 count(常量)来替代 count(*)?117目录为什么禁止使用 Apache Beanutils 进行属性的 copy?在日常开发中,我们经常需要给对象进行赋值,通常会调用其 set/get 方法,有些时候,如果我们

3、要转换的两个对象之间属性大致相同,会考虑使用属性拷贝工具进行。如我们经常在代码中会对一个数据结构封装成 DO、SDO、DTO、VO 等,而这些 Bean 中的大部分属性都是一样的,所以使用属性拷贝类工具可以帮助我们节省大量的 set 和 get 操作。市面上有很多类似的工具类,比较常用的有1.Spring BeanUtils2.Cglib BeanCopier3.Apache BeanUtils4.Apache PropertyUtils5.Dozer那么,我们到底应该选择哪种工具类更加合适呢?为什么 Java 开发手册中提到禁止使用 Apache BeanUtils 呢?由于篇幅优先,关于这

4、几种工具类的用法及区别,还有到底是什么是浅拷贝和深为什么禁止使用 Apache Beanutils 进行属性的 copy?5拷贝不在本文的讨论范围内。本文主要聚焦于对比这几个类库的性能问题。性能对比No Data No BB,我们就来写代码来对比下这几种框架的性能情况。代码示例如下:首先定义一个 PersonDO 类:public class PersonDO private Integer id;private String name;private Integer age;private Date birthday;/省略 setter/getter再定义一个 PersonDTO 类:pu

5、blic class PersonDTO private String name;private Integer age;private Date birthday;然后进行测试类的编写:使用 Spring BeanUtils 进行属性拷贝:private void mappingBySpringBeanUtils(PersonDO personDO,int times)StopWatch stopwatch=new StopWatch();stopwatch.start();for(int i=0;i 为什么禁止使用 Apache Beanutils 进行属性的 copy?stopwatch

6、.stop();System.out.println(mappingBySpringBeanUtils cost:+stopwatch.getTotalTimeMillis();其中的 StopWatch 用于记录代码执行时间,方便进行对比。使用 Cglib BeanCopier 进行属性拷贝:private void mappingByCglibBeanCopier(PersonDO personDO,int times)StopWatch stopwatch=new StopWatch();stopwatch.start();for(int i=0;i times;i+)PersonDTO

7、 personDTO=new PersonDTO();BeanCopier copier=BeanCopier.create(PersonDO.class,PersonDTO.class,false);copier.copy(personDO,personDTO,null);stopwatch.stop();System.out.println(mappingByCglibBeanCopier cost:+stopwatch.getTotalTimeMillis();使用 Apache BeanUtils 进行属性拷贝:private void mappingByApacheBeanUtils

8、(PersonDO personDO,int times)throws InvocationTargetException,IllegalAccessException StopWatch stopwatch=new StopWatch();stopwatch.start();for(int i=0;i times;i+)PersonDTO personDTO=new PersonDTO();BeanUtils.copyProperties(personDTO,personDO);stopwatch.stop();System.out.println(mappingByApacheBeanUt

9、ils cost:+stopwatch.getTotalTimeMillis();使用 Apache PropertyUtils 进行属性拷贝:为什么禁止使用 Apache Beanutils 进行属性的 copy?7private void mappingByApachePropertyUtils(PersonDO personDO,int times)throws InvocationTargetException,IllegalAccessException,NoSuchMethodException StopWatch stopwatch=new StopWatch();stopwat

10、ch.start();for(int i=0;i 为什么禁止使用 Apache Beanutils 进行属性的 copy?mapperTest.mappingByApacheBeanUtils(personDO,1000000);得到结果如下:画了一张折线图更方便大家进行对比综上,我们基本可以得出结论,在性能方面,Spring BeanUtils 和 Cglib Bean-Copier 表现比较不错,而 Apache PropertyUtils、Apache BeanUtils 以及 Dozer则表现的很不好。所以,如果考虑性能情况的话,建议大家不要选择 Apache PropertyUtil

11、s、为什么禁止使用 Apache Beanutils 进行属性的 copy?9Apache BeanUtils 以及 Dozer 等工具类。很多人会不理解,为什么大名鼎鼎的 Apache 开源出来的的类库性能确不高呢?这不像是 Apache 的风格呀,这背后导致性能低下的原因又是什么呢?其实,是因为 Apache BeanUtils 力求做得完美,在代码中增加了非常多的校验、兼容、日志打印等代码,过度的包装导致性能下降严重。总结本文通过对比几种常见的属性拷贝的类库,分析得出了这些工具类的性能情况,最终也验证了Java 开发手册中提到的”Apache BeanUtils 效率低”的事实。但是本文

12、只是站在性能这一单一角度进行了对比,我们在选择一个工具类的时候还会有其他方面的考虑,比如使用成本、理解难度、兼容性、可扩展性等,对于这种拷贝类工具类,我们还会考虑其功能是否完善等。就像虽然 Dozer 性能比较差,但是他可以很好的和 Spring 结合,可以通过配置文件等进行属性之间的映射等,也受到了很多开发者的喜爱。本文用到的第三方类库的 maven 依赖如下:commons-beanutils commons-beanutils 1.9.4 commons-logging commons-logging 1.1.2 org.springframework10为什么禁止使用 Apache B

13、eanutils 进行属性的 copy?org.springframework.beans 3.1.1.RELEASE cglib cglib-nodep 2.2.2 net.sf.dozer dozer 5.5.1 org.slf4j slf4j-api 1.7.7 org.slf4j jul-to-slf4j 1.7.7 org.slf4j jcl-over-slf4j 1.7.7 org.slf4j log4j-over-slf4j 1.7.7 org.slf4j slf4j-jdk14 1.7.7为什么要求日期格式化时必须有使用 y 表示年,而不能用 Y?在 Java 中进行日期处理大

14、家一定都不陌生,我们经常会需要在代码中进行日期的转换、日期的格式化等操作。而一般我们进行日期格式化的时候都会使用 SimpleDateFormat 工具,之前我们有一篇文章介绍过 SimpleDateFormat 的线程安全问题,这一篇文章再来介绍一个和 SimpleDateFormat 有关,很容易被忽视,而一旦忽视可能导致大故障的问题。SimpleDateFormatSimpleDateFormat 是 Java 提供的一个格式化和解析日期的工具类。它允许进行格式化(日期-文本)、解析(文本-日期)和规范化。SimpleDateFormat 使得可以选择任何用户定义的日期-时间格式的模式。

15、在 Java 中,可以使用 SimpleDateFormat 的 format 方法,将一个 Date 类型转化成 String 类型,并且可以指定输出格式。/Date 转 StringDate data=new Date();SimpleDateFormat sdf=new SimpleDateFormat(yyyy-MM-dd HH:mm:ss);String dataStr=sdf.format(data);System.out.println(dataStr);以上代码,转换的结果是:2018-11-25 13:00:00,日期和时间格式由”日期和时间模式”字符串指定。如果你想要转换成

16、其他格式,只要指定不同的时间模式就行了。12为什么要求日期格式化时必须有使用 y 表示年,而不能用 Y?在 Java 中,可以使用 SimpleDateFormat 的 parse 方法,将一个 String 类型转化成 Date 类型。/String 转 DataSystem.out.println(sdf.parse(dataStr);日期和时间模式表达方法在使用 SimpleDateFormat 的时候,需要通过字母来描述时间元素,并组装成想要的日期和时间模式。常用的时间元素和字母的对应表(JDK 1.8)如下:模式字母通常是重复的,其数量确定其精确表示。如前面我们用过的”yyyy-MM

17、-dd HH:mm:ss”。我们知道其中的 y 其实是 year 的缩写,所以我们都知道使用y 来表示年,默认情况下,我们都会使用 y 而不是 Y,那么这两者之间有何区别呢?一旦用错了会带来什么后果呢?其实在规定中,y 表示 year,而 Y 表示 Week Year!为什么要求日期格式化时必须有使用 y 表示年,而不能用 Y?为什么要求日期格式化时必须有使用 y 表示年,而不能用 Y?了一个国际规范:ISO 8601。国际标准化组织的国际标准 ISO 8601 是日期和时间的表示方法,全称为数据存储和交换形式信息交换日期和时间的表示方法。在 ISO 8601 中。对于一年的第一个日历星期有以

18、下四种等效说法:1,本年度第一个星期四所在的星期;2,1 月 4 日所在的星期;3,本年度第一个至少有 4 天在同一星期内的星期;4,星期一在去年 12 月 29 日至今年 1 月 4 日以内的星期;根据这个标准,我们可以推算出:2020 年第一周:2019.12.29-2020.1.4所 以,根 据 ISO 8601 标 准,2019 年 12 月 29 日、2019 年 12 月 30 日、2019 年 12 月 31 日这两天,其实不属于 2019 年的最后一周,而是属于 2020 年的第一周。JDK 针对 ISO 8601 提供的支持根据 ISO 8601 中关于日历星期和日表示法的定

19、义,2019.12.29-2020.1.4 是2020 年的第一周。我们希望输入一个日期,然后程序告诉我们,根据 ISO 8601 中关于日历日期的定义,这个日期到底属于哪一年。比如我输入 2019-12-20,他告诉我是 2019;而我输入 2019-12-30 的时候,他告诉我是 2020。为了提供这样的数据,Java 7 引入了YYYY作为一个新的日期模式来作为标识。使用YYYY作为标识,再通过 SimpleDateFormat 就可以得到一个日期所属的周属于哪一年了。所以,我们通过代码可以验证:为什么要求日期格式化时必须有使用 y 表示年,而不能用 Y?15public class W

20、eekYearTest public static void main(String args)SimpleDateFormat sdf=new SimpleDateFormat(yyyy-MM-dd);SimpleDateFormat sdf1=new SimpleDateFormat(YYYY);System.out.println(sdf1.format(sdf.parse(2019-12-01);System.out.println(sdf1.format(sdf.parse(2019-12-30);System.out.println(sdf1.format(sdf.parse(20

21、20-01-01);输出结果为:201920202020因为有这样的情况,所以我们日常开发的时候,如果把 y 写成了 Y,那就可能导致日期输出的结果不符合我们的预期。当我们要表示日期的时候,一定要使用 yyyy-MM-dd 而不是 YYYY-MM-dd,这两者的返回结果大多数情况下都一样,但是极端情况就会有问题了。而这一个要求,在Java 开发手册中也有类似的规定:好啦,大家快去排查下你的代码,有没有 YYYY-MM-dd这种形式的代码吧,如果有的话,一定要改掉哦!Java 开发手册-泰山版提到的三目运算符的空指针问题到底是个怎么回事?Java 开发手册发布了泰山版,这个名字起的不错,一览众山

22、小。新版据说新增了 30+规约,其中有一条规约引起了作者的关注,因为这个问题我很久之前遇到过,曾经在博客中也记录过。最初遇到这个问题的是我的同事,他在代码中使用了三目运算符,代码在线上运行的时候发生了 NPE,经过排查,发现原来是三目运算符和自动拆装箱之间有一定的关系,导致了空指针。趁着最新的开发手册中也提到了这个点,于是把之前的文章内容翻出来并重新整理了一下,带大家一起回顾下这个知识点。一、三目运算符对于条件表达式 b?x:y,先计算条件 b,然后进行判断。如果 b 的值为 true,Java 开发手册-泰山版提到的三目运算符的空指针问题到底是个怎么回事?17计算 x 的值,运算结果为 x

23、的值;否则,计算 y 的值,运算结果为 y 的值。一个条件表达式从不会既计算 x,又计算 y。条件运算符是右结合的,也就是说,从右向左分组计算。例如,a?b:c?d:e 将按 a?b:(c?d:e)执行。二、自动装箱与自动拆箱基本数据类型的自动装箱(autoboxing)、拆箱(unboxing)是自 J2SE 5.0 开始提供的功能。一般我们要创建一个类的对象实例的时候,我们会这样:Class a=new Class(parameters);当我们创建一个 Integer 对象时,却可以这样:Integer i=100;(注意:和 int i=100;是有区别的)实际上,执行上面那句代码的时

24、候,系统为我们执行了:Integer i=Inte-ger.valueOf(100);这里暂且不讨论这个原理是怎么实现的(何时拆箱、何时装箱),也略过普通数据类型和对象类型的区别。我们可以理解为,当我们自己写的代码符合装(拆)箱规范的时候,编译器就会自动帮我们拆(装)箱。那么,这种不被程序员控制的自动拆(装)箱会不会存在什么问题呢?三、问题回顾首先,通过你已有的经验看一下下面这段代码:Map map=new HashMap();Boolean b=(map!=null?map.get(test):false);18 Java 开发手册-泰山版提到的三目运算符的空指针问题到底是个怎么回事?以上这

25、段代码,是我们在不注意的情况下有可能经常会写的一类代码(在很多时候我们都爱使用三目运算符)。当然,这段代码是存在问题的,执行该代码,会报NPE。Exception in thread main java.lang.NullPointerException首先可以明确的是,既然报了空指针,那么一定是有些地方调用了一个 null 的对象的某些方法。在这短短的两行代码中,看上去只有一处方法调用 map.get(test),但是我们也都是知道,map 已经事先初始化过了,不会是 Null,那么到底是哪里有空指针呢。我们接下来反编译一下该代码。看看我们写的代码在经过编译器处理之后变成了什么样。反编译后代

26、码如下:HashMap hashmap=new HashMap();Boolean boolean1=Boolean.valueOf(hashmap=null?false:(Boolean)hashmap.get(test).booleanValue();看完这段反编译之后的代码之后,经过分析我们大概可以知道问题出在哪里。(Boolean)hashmap.get(test).booleanValue()的执行过程及结果如下:hashmap.get(“test”)-null;(Boolean)null-null;null.booleanValue()-报错好,问题终于定位到了。那么接下来看看如何

27、解决该问题以及为什么会出现这种问题。Java 开发手册-泰山版提到的三目运算符的空指针问题到底是个怎么回事?b:i1:i2;,就要求 i1 和 i2 的类型都必须是 Person才行。因为 Java 中存在一种特殊的情况,那就是基本数据类型和包装数据类型可以通过自动拆装箱的方式互相转换。即可以定义 int i=new Integer(10);也可以定义Integer i=10;那如果,三目运算符的第二位和第三位的操作数的类型分别是基本数据类型和包装类型对象时,就需要有一方需要进行自动拆装箱。那到底如何做的呢,根据三目运算符的语法规范。参见 jls-15.25,摘要如下:If the secon

28、d and third operands have the same type(which may be the null type),then that is the type of the conditional expression.If one of the second and third operands is of primitive type T,and the type of the other is the result of applying boxing conversion(5.1.7)to T,then the type of the condition-al ex

29、pression is T.If one of the second and third operands is of the null type and the type of the other is a reference type,then the type of the conditional expression is that reference type.简单的来说就是:当第二,第三位操作数分别为基本类型和对象时,其中的对象就会拆箱为基本类型进行操作。所以,结果就是:由于使用了三目运算符,并且第二、第三位操作数分别是基本类型和对象。所以对对象进行拆箱操作,由于该对象为 null

30、,所以在拆箱过程中调用20 Java 开发手册-泰山版提到的三目运算符的空指针问题到底是个怎么回事?null.booleanValue()的时候就报了 NPE。五、问题解决如果代码这么写,就不会报错:Map map=new HashMap();Boolean b=(map!=null?map.get(test):Boolean.FALSE);就是保证了三目运算符的第二第三位操作数都为对象类型。这和三目运算符有关。为什么建议初始化 HashMap 的容量大小?很多人在通过阅读源码的方式学习 Java,这是个很好的方式。而 JDK 的源码自然是首选。在 JDK 的众多类中,我觉得 HashMap

31、及其相关的类是设计的比较好的。很多人读过 HashMap 的代码,不知道你们有没有和我一样,觉得 HashMap 中关于容量相关的参数定义的太多了,傻傻分不清楚。先来看一下,HashMap 中都定义了哪些成员变量。上面是一张 HashMap 中主要的成员变量的图,其中有一个是我们本文主要关注的:size、loadFactor、threshold、DEFAULT_LOAD_FACTOR 和 DEFAULT_INITIAL_CAPACITY。我们先来简单解释一下这些参数的含义,然后再分析他们的作用。22为什么建议初始化 HashMap 的容量大小?HashMap 类中有以下主要成员变量:trans

32、ient int size;记录了 Map 中 KV 对的个数 loadFactor 装载印子,用来衡量 HashMap 满的程度。loadFactor 的默认值为 0.75f(static final float DEFAULT_LOAD_FACTOR=0.75f;)。int threshold;临界值,当实际 KV 个数超过 threshold 时,HashMap 会将容量扩容,threshold 容量*加载因子 除了以上这些重要成员变量外,HashMap 中还有一个和他们紧密相关的概念:capacity 容量,如果不指定,默认容量是 16(static final int DEFAULT

33、_INI-TIAL_CAPACITY=1 4;)可能看完了你还是有点蒙,size 和 capacity 之间有啥关系?为啥要定义这两个变量。loadFactor 和 threshold 又是干啥的?size 和 capacityHashMap 中的 size 和 capacity 之间的区别其实解释起来也挺简单的。我们知道,HashMap 就像一个“桶”,那么 capacity 就是这个桶“当前”最多可以装多少元素,而 size 表示这个桶已经装了多少元素。来看下以下代码:Map map=new HashMap();map.put(hollis,hollischuang);Class mapT

34、ype=map.getClass();Method capacity=mapType.getDeclaredMethod(capacity);capacity.setAccessible(true);System.out.println(capacity:+capacity.invoke(map);Field size=mapType.getDeclaredField(size);size.setAccessible(true);为什么建议初始化 HashMap 的容量大小?23 System.out.println(size:+size.get(map);我们定义了一个新的 HashMap,

35、并向其中 put 了一个元素,然后通过反射的方式打印 capacity 和 size。输出结果为:capacity:16、size:1默认情况下,一个 HashMap 的容量(capacity)是 16,设计成 16 的好处我在全网把 Map 中的 hash()分析的最透彻的文章,别无二家中也简单介绍过,主要是可以使用按位与替代取模来提升 hash 的效率。为什么我刚刚说 capacity 就是这个桶“当前”最多可以装多少元素呢?当前怎么理解呢。其实,HashMap 是具有扩容机制的。在一个 HashMap 第一次初始化的时候,默认情况下他的容量是 16,当达到扩容条件的时候,就需要进行扩容了

36、,会从 16 扩容成 32。我们知道,HashMap 的重载的构造函数中,有一个是支持传入 initialCapacity的,那么我们尝试着设置一下,看结果如何。Map map=new HashMap(1);Class mapType=map.getClass();Method capacity=mapType.getDeclaredMethod(capacity);capacity.setAccessible(true);System.out.println(capacity:+capacity.invoke(map);Map map=new HashMap(7);Class mapType

37、=map.getClass();Method capacity=mapType.getDeclaredMethod(capacity);capacity.setAccessible(true);System.out.println(capacity:+capacity.invoke(map);Map map=new HashMap(9);Class mapType=map.getClass();Method capacity=mapType.getDeclaredMethod(capacity);capacity.setAccessible(true);System.out.println(c

38、apacity:+capacity.invoke(map);24为什么建议初始化 HashMap 的容量大小?分别执行以上 3 段代码,分别输出:capacity:2、capacity:8、capacity:16。也就是说,默认情况下 HashMap 的容量是 16,但是,如果用户通过构造函数指定了一个数字作为容量,那么 Hash 会选择大于该数字的第一个 2 的幂作为容量。(1-1、7-8、9-16)这里有一个小建议:在初始化 HashMap 的时候,应该尽量指定其大小。尤其是当你已知 map 中存放的元素个数时。(阿里巴巴 Java 开发规约)loadFactor 和 threshold前

39、面我们提到过,HashMap 有扩容机制,就是当达到扩容条件时会进行扩容,从 16 扩容到 32、64、128那么,这个扩容条件指的是什么呢?其实,HashMap 的扩容条件就是当 HashMap 中的元素个数(size)超过临界值(threshold)时就会自动扩容。在 HashMap 中,threshold=loadFactor*capacity。loadFactor 是装载因子,表示 HashMap 满的程度,默认值为 0.75f,设置成0.75 有一个好处,那就是 0.75 正好是 3/4,而 capacity 又是 2 的幂。所以,两个数的乘积都是整数。对于一个默认的 HashMap

40、 来说,默认情况下,当其 size 大于 12(16*0.75)时就会触发扩容。验证代码如下:Map map=new HashMap();map.put(hollis1,hollischuang);map.put(hollis2,hollischuang);为什么建议初始化 HashMap 的容量大小?25 map.put(hollis3,hollischuang);map.put(hollis4,hollischuang);map.put(hollis5,hollischuang);map.put(hollis6,hollischuang);map.put(hollis7,hollischu

41、ang);map.put(hollis8,hollischuang);map.put(hollis9,hollischuang);map.put(hollis10,hollischuang);map.put(hollis11,hollischuang);map.put(hollis12,hollischuang);Class mapType=map.getClass();Method capacity=mapType.getDeclaredMethod(capacity);capacity.setAccessible(true);System.out.println(capacity:+cap

42、acity.invoke(map);Field size=mapType.getDeclaredField(size);size.setAccessible(true);System.out.println(size:+size.get(map);Field threshold=mapType.getDeclaredField(threshold);threshold.setAccessible(true);System.out.println(threshold:+threshold.get(map);Field loadFactor=mapType.getDeclaredField(loa

43、dFactor);loadFactor.setAccessible(true);System.out.println(loadFactor:+loadFactor.get(map);map.put(hollis13,hollischuang);Method capacity=mapType.getDeclaredMethod(capacity);capacity.setAccessible(true);System.out.println(capacity:+capacity.invoke(map);Field size=mapType.getDeclaredField(size);size.

44、setAccessible(true);System.out.println(size:+size.get(map);Field threshold=mapType.getDeclaredField(threshold);threshold.setAccessible(true);System.out.println(threshold:+threshold.get(map);Field loadFactor=mapType.getDeclaredField(loadFactor);loadFactor.setAccessible(true);System.out.println(loadFa

45、ctor:+loadFactor.get(map);输出结果:26为什么建议初始化 HashMap 的容量大小?capacity:16size:12threshold:12loadFactor:0.75capacity:32size:13threshold:24loadFactor:0.75当 HashMap 中的元素个数达到 13 的时候,capacity 就从 16 扩容到 32 了。HashMap 中还提供了一个支持传入 initialCapacity,loadFactor 两个参数的方法,来初始化容量和装载因子。不过,一般不建议修改 loadFactor 的值。总之,HashMap 中

46、 size 表示当前共有多少个 KV 对,capacity 表示当前HashMap 的容量是多少,默认值是 16,每次扩容都是成倍的。loadFactor 是装载因子,当 Map 中元素个数超过 loadFactor*capacity 的值时,会触发扩容。loadFactor*capacity 可以用 threshold 表示。PS:文中分析基于 JDK1.8.0_73在上面的内容我们说明了 HashMap 中和容量相关的几个概念,简单介绍了一下HashMap 的扩容机制。文中我们提到,默认情况下 HashMap 的容量是 16,但是,如果用户通过构造函数指定了一个数字作为容量,那么 Hash

47、 会选择大于该数字的第一个 2 的幂作为容量。(3-4、7-8、9-16)接下来我们再来深入学习下,到底应不应该设置 HashMap 的默认容量?如果真的要设置 HashMap 的初始容量,我们应该设置多少?为什么要设置 HashMap 的初始化容量我们之前提到过,Java 开发手册中建议我们设置 HashMap 的初始化容量。为什么建议初始化 HashMap 的容量大小?27那么,为什么要这么建议?你有想过没有。我们先来写一段代码在 JDK 1.7(jdk1.7.0_79)下面来分别测试下,在不指定初始化容量和指定初始化容量的情况下性能情况如何。(jdk 8 结果会有所不同,我会在后面的文章

48、中分析)public static void main(String args)int aHundredMillion=10000000;Map map=new HashMap();long s1=System.currentTimeMillis();for(int i=0;i aHundredMillion;i+)map.put(i,i);long s2=System.currentTimeMillis();System.out.println(未初始化容量,耗时:+(s2-s1);Map map1=new HashMap(aHundredMillion/2);long s5=System.

49、currentTimeMillis();for(int i=0;i aHundredMillion;i+)map1.put(i,i);long s6=System.currentTimeMillis();System.out.println(初始化容量 5000000,耗时:+(s6-s5);Map map2=new HashMap(aHundredMillion);long s3=System.currentTimeMillis();for(int i=0;i 为什么建议初始化 HashMap 的容量大小?以上代码不难理解,我们创建了 3 个 HashMap,分别使用默认的容量(16)、使用

50、元素个数的一半(5 千万)作为初始容量、使用元素个数(一亿)作为初始容量进行初始化。然后分别向其中 put 一亿个 KV。输出结果:未初始化容量,耗时:14419初始化容量 5000000,耗时:11916初始化容量为 10000000,耗时:7984从结果中,我们可以知道,在已知 HashMap 中将要存放的 KV 个数的时候,设置一个合理的初始化容量可以有效的提高性能。当然,以上结论也是有理论支撑的。我们在上文介绍过,HashMap 有扩容机制,就是当达到扩容条件时会进行扩容。HashMap 的扩容条件就是当 HashMap中的元素个数(size)超过临界值(threshold)时就会自动

展开阅读全文
相似文档                                   自信AI助手自信AI助手
猜你喜欢                                   自信AI导航自信AI导航
搜索标签

当前位置:首页 > 研究报告 > 其他

移动网页_全站_页脚广告1

关于我们      便捷服务       自信AI       AI导航        获赠5币

©2010-2024 宁波自信网络信息技术有限公司  版权所有

客服电话:4008-655-100  投诉/维权电话:4009-655-100

gongan.png浙公网安备33021202000488号   

icp.png浙ICP备2021020529号-1  |  浙B2-20240490  

关注我们 :gzh.png    weibo.png    LOFTER.png 

客服