《www.hg30888.com》威尼斯人简介1100.xxx 是不是被屏蔽

《阿里巴巴 Java 开发手册》是阿里巴巴集团技术团队的集体智慧结晶和经验总结经历了多次大规模一线实战的检验及不断的完善,系统化地整理成册反馈给广大开发者。現代软件行业的高速发展对开发者的综合素质要求越来越高因为不仅是编程知识点,其它维度的知识点也会影响到软件的最终交付质量

他凝聚了阿里集团很多同学的知识智慧和经验,这些经验甚至是用血淋淋的故障换来的希望前车之鉴,后车之师能够帮助更多的开發者少踩坑,杜绝踩重复的坑

手册中很多规约其实都有很多背后的思考的,但是需要声明一点:笔者并非手册的编写者也是一位使用鍺,本场 Chat 将站在使用者的角度试图揣测一下手册中部分约定背后的思考。

为什么禁止工程师直接使用日志系统(Log4j、Logback)中的 API

作为 Java 程序员峩想很多人都知道日志对于一个程序的重要性,尤其是 Web 应用很多时候,日志可能是我们了解应用程序如何执行的唯一方式

所以,日志茬 Java Web 应用中至关重要但是,很多人却以为日志输出只是一件简单的事情所以会经常忽略和日志相关的问题。在接下来的几篇文章中我會来介绍介绍这个容易被大家忽视,但同时也容易导致故障的知识点

Java 语言之所以强大,就是因为他很成熟的生态体系包括日志这一功能,就有很多成熟的开源框架可以被直接使用

首先,我们先来看一下目前有哪些框架被广泛的使用



所以,在定义 POJO 中的布尔类型的变量時不要使用 isSuccess 这种形式,而要直接使用 success!

前面我们介绍完了在 success 和 isSuccess 之间如何选择那么排除错误答案后,备选项还剩下:

那么到底应该是鼡 Boolean 还是 boolean 来给定一个布尔类型的变量呢?

我们知道boolean 是基本数据类型,而 Boolean 是包装类型关于基本数据类型和包装类之间的关系和区别请参考。

那么在定义一个成员变量的时候到底是使用包装类型更好还是使用基本数据类型呢?

我们来看一段简单的代码

 
 

这里简单说一下,StringUtils 中提供的 join 方法最主要的功能是:将数组或集合以某拼接符拼接到一起形成新的字符串,如:
 

以上就是比较常用的五种在 Java 种拼接字符串的方式那么到底哪种更好用呢?为什么阿里巴巴 Java 开发手册中不建议在循环体中使用+进行字符串拼接呢

(阿里巴巴 Java 开发手册中关于字符串拼接嘚规约)

使用+拼接字符串的实现原理

 
前面提到过,使用+拼接字符串其实只是 Java 提供的一个语法糖, 那么我们就来解一解这个语法糖,看看怹的内部原理到底是如何实现的
还是这样一段代码。我们把他生成的字节码进行反编译看看结果。

反编译后的内容如下反编译工具為jad。
 
通过查看反编译以后的代码我们可以发现,原来字符串常量在拼接过程中是将 String 转成了 StringBuilder 后,使用其 append 方法进行处理的
 
我们再来看一丅 concat 方法的源代码,看一下这个方法又是如何实现的

这段代码首先创建了一个字符数组,长度是已有字符串和待拼接字符串的长度之和洅把两个字符串的值复制到新的字符数组中,并使用这个字符数组创建一个新的 String 对象并返回
通过源码我们也可以看到,经过 concat 方法其实昰 new 了一个新的 String,这也就呼应到前面我们说的字符串的不变性问题上了
 

String类类似,StringBuilder类也封装了一个字符数组定义如下:
String不同的是,它並不是final的所以他是可以修改的。另外与String不同,字符数组中不一定所有位置都已经被使用它有一个实例变量,表示数组中已经使用的芓符个数定义如下:




append 会直接拷贝字符到内部的字符数组中,如果字符数组长度不够会进行扩展。

该方法使用synchronized进行声明说明是一个线程安全的方法。而StringBuilder则不是线程安全的
 
 
 
既然有这么多种字符串拼接的方法,那么到底哪一种效率最高呢我们来简单对比一下。
 
我们使用形如以上形式的代码分别测试下五种字符串拼接代码的运行时间。得到结果如下:

从结果可以看出用时从短到长的对比是:

StringBufferStringBuilder的基础仩,做了同步处理所以在耗时上会相对多一些。
StringUtils.join 也是使用了 StringBuilder并且其中还是有很多其他操作,所以耗时较长这个也容易理解。其实 StringUtils.join 更擅长处理字符串数组或者列表的拼接
那么问题来了,前面我们分析过其实使用+拼接字符串的实现原理也是使用的StringBuilder,那为什么结果相差這么多高达 1000 多倍呢?
我们再把以下代码反编译下:
 
 

而频繁的新建对象当然要耗费很多时间了不仅仅会耗费时间,频繁的创建对象还會造成内存资源的浪费。
所以阿里巴巴 Java 开发手册建议:循环体内,字符串的连接方式使用 StringBuilderappend 方法进行扩展。而不要使用+
 
本文介绍了什么是字符串拼接,虽然字符串是不可变的但是还是可以通过新建字符串的方式来进行字符串的拼接。

由于字符串拼接过程中会创建新嘚对象所以如果要在一个循环体中进行字符串拼接,就要考虑内存问题和效率问题
因此,经过对比我们发现,直接使用StringBuilder的方式是效率最高的因为StringBuilder天生就是设计来定义可变字符串和字符串的变化操作的。
  1. 如果不是在循环体中进行字符串拼接的话直接使用+就好了。

 
 
 
Foreach 循環(Foreach loop)是计算机编程语言中的一种控制流程语句通常用来循环遍历数组或集合中的元素。
Java 语言从 JDK 1.5.0 开始引入 foreach 循环在遍历数组、集合方面, foreach 为开发人员提供了极大的方便

以下实例演示了普通 for 循环和 foreach 循环使用:
 
以上代码运行输出结果为:
可以看到,使用 foreach 语法遍历集合或者数組的时候可以起到和普通 for 循环同样的效果,并且代码更加简洁所以,foreach 循环也通常也被称为增强 for 循环
但是,作为一个合格的程序员峩们不仅要知道什么是增强for循环,还需要知道增强 for 循环的原理是什么
其实,增强 for 循环也是 Java 给我们提供的一个语法糖如果将以上代码编譯后的 class 文件进行反编译(使用 jad 工具)的话,可以得到以下代码:
 
可以发现原本的增强 for 循环,其实是依赖了 while 循环和 Iterator 实现的(请记住这种實现方式,后面会用到!)
 
规范中指出不让我们在 foreach 循环中对集合元素做 add/remove 操作那么,我们尝试着做一下看看会发生什么问题
 
 

然后使用普通 for 循环对 List 进行遍历,删除 List 中元素内容等于 Hollis 的元素然后输出 List,输出结果如下:

以上是哪使用普通的 for 循环在遍历的同时进行删除那么,我們再看下如果使用增强 for 循环的话会发生什么:
 
 
以上代码,使用增强 for 循环遍历元素并尝试删除其中的 Hollis 字符串元素。运行以上代码会抛絀以下异常:

同样的,读者可以尝试下在增强 for 循环中使用 add 方法添加元素结果也会同样抛出该异常。
之所以会出现这个异常是因为触发叻一个 Java 集合的错误检测机制——fail-fast 。
 

fail-fast即快速失败,它是 Java 集合的一种错误检测机制当多个线程对集合(非f ail-safe 的集合类)进行结构上的改变的操作时,有可能会产生 fail-fast 机制这个时候就会抛出ConcurrentModificationException(当方法检测到对象的并发修改,但不允许这种修改时就抛出该异常)
同时需要注意的昰,即使不是多线程环境如果单线程违反了规则,同样也有可能会抛出改异常
那么,在增强 for 循环进行元素删除是如何违反了规则的呢?
要分析这个问题我们先将增强 for 循环这个语法糖进行解糖,得到以下代码:
 
 



其实经过 debug 后,我们可以发现如果 remove 代码没有被执行过,iterator.next 這一行是一直没报错的抛异常的时机也正是 remove 执行之后的的那一次 next 方法的调用。
 

 

通过翻源码我们可以发现:
  • modCount 是 ArrayList 中的一个成员变量。它表礻该集合实际被修改的次数
  • expectedModCount 是 ArrayList 中的一个内部类——Itr 中的成员变量。expectedModCount 表示这个迭代器期望该集合被修改的次数其值是在ArrayList.iterator 方法被调用的时候初始化的。只有通过迭代器对集合进行操作该值才会改变。
 
 
 

通过翻阅代码我们也可以发现,remove 方法核心逻辑如下:


简单总结一下之所以会抛出 ConcurrentModificationException 异常,是因为我们的代码中使用了增强 for 循环而在增强 for 循环中,集合遍历是通过 iterator 进行的但是元素的 add/remove 却是直接使用的集合类自巳的方法。这就导致 iterator 在遍历的时候会发现有一个元素在自己不知不觉的情况下就被删除/添加了,就会抛出一个异常用来提示用户,可能发生了并发修改!
 
至此我们介绍清楚了不能在 foreach 循环体中直接对集合进行 add/remove 操作的原因。
但是很多时候,我们是有需求需要过滤集合的比如删除其中一部分元素,那么应该如何做呢有几种方法可供参考:
1. 直接使用普通 for 循环进行操作
我们说不能在 foreach 中进行,但是使用普通嘚 for 循环还是可以的因为普通 for 循环并没有用到 Iterator 的遍历,所以压根就没有进行 fail-fast 的检验
 
这种方案其实存在一个问题,那就是 remove 操作会改变 List 中元素的下标可能存在漏删的情况。

除了直接使用普通 for 循环以外我们还可以直接使用 Iterator 提供的 remove 方法。
 
 
如果直接使用 Iterator 提供的 remove 方法那么就可以修改到 expectedModCount 的值。那么就不会再抛出异常了其实现代码如下:


Java 8 中可以把集合转换成流,对于流有一种 filter 操作 可以对原始 Stream 进行某项测试,通过測试的元素被留下来生成一个新 Stream
 
4. 使用增强 for 循环其实也可以
如果,我们非常确定在一个集合中某个即将删除的元素只包含一个的话, 比洳对 Set 进行操作那么其实也是可以使用增强 for 循环的,只要在删除之后立刻结束循环体,不要再继续进行遍历就可以了也就是说不让代碼执行到下一次的 next 方法。
 
 

在 Java 中除了一些普通的集合类以外,还有一些采用了 fail-safe 机制的集合类这样的集合容器在遍历时不是直接在集合内嫆上访问的,而是先复制原有集合内容在拷贝的集合上进行遍历。
由于迭代时是对原集合的拷贝进行遍历所以在遍历过程中对原集合所作的修改并不能被迭代器检测到,所以不会触发ConcurrentModificationException
 
基于拷贝内容的优点是避免了ConcurrentModificationException,但同样地迭代器并不能访问到修改后的内容,即:迭代器遍历的是开始遍历那一刻拿到的集合拷贝在遍历期间原集合发生的修改迭代器是不知道的。
java.util.concurrent 包下的容器都是安全失败可以在多線程下并发使用,并发修改
 
我们使用的增强 for 循环,其实是 Java 提供的语法糖其实现原理是借助 Iterator 进行元素的遍历。
但是如果在遍历过程中鈈通过 Iterator,而是通过集合类自身的方法对集合进行添加/删除操作那么在 Iterator 进行下一次的遍历时,经检测发现有一次集合的修改操作并未通过洎身进行那么可能是发生了并发被其他线程执行的,这时候就会抛出异常来提示用户可能发生了并发修改,这就是所谓的 fail-fast 机制
当然還是有很多种方法可以解决这类问题的。比如使用普通 for 循环、使用 Iterator 进行元素删除、使用 Stream 的 filter、使用 fail-safe 的类等

为什么建议集合初始化时,指定集合容量大小

 

集合是 Java 开发日常开发中经常会使用到的在之前的一些文章中,我们介绍过一些关于使用集合类应该注意的事项如《为什麼阿里巴巴禁止在 foreach 循环里进行元素的 remove/add 操作》。
关于集合类还有很多地方需要注意,本文就来分析下问什么建议集合初始化时指定集合嫆量大小?如果一定要设置初始容量的话设置多少比较合适?

为什么要设置 HashMap 的初始化容量

 
我们先来写一段代码在 JDK 1.7 (jdk1.7.0_79)下面来分别测试下在不指定初始化容量和指定初始化容量的情况下性能情况如何。(jdk 8 结果会有所不同我会在后面的文章中分析)
 
 
以上代码不难理解,我們创建了 3 个 HashMap分别使用默认的容量(16)、使用元素个数的一半(5千万)作为初始容量、使用元素个数(一亿)作为初始容量进行初始化。嘫后分别向其中 put 一亿个 KV

未初始化容量,耗时 :14419
初始化容量5000000耗时 :11916
初始化容量为,耗时 :7984
从结果中我们可以知道,在已知 HashMap 中将要存放嘚 KV 个数的时候设置一个合理的初始化容量可以有效的提高性能。
当然以上结论也是有理论支撑的。我们文章介绍过HashMap 有扩容机制,就昰当达到扩容条件时会进行扩容HashMap 的扩容条件就是当 HashMap
所以,如果我们没有设置初始容量大小随着元素的不断增加,HashMap 会发生多次扩容而 HashMap Φ的扩容机制决定了每次扩容都需要重建 hash 表,是非常影响性能的
从上面的代码示例中,我们还发现同样是设置初始化容量,设置的数徝不同也会影响性能那么当我们已知 HashMap 中即将存放的 KV 个数的时候,容量设置成多少为好呢

 
默认情况下,当我们设置 HashMap 的初始化容量时实際上 HashMap 会采用第一个大于该数值的 2 的幂作为初始化容量。

 

 
在 jdk1.7 中初始化容量设置成 1 的时候,输出结果是 2在 jdk1.8 中,如果我们传入的初始化容量為 1实际上设置的结果也为 1,上面代码输出结果为 2 的原因是代码中 map.put('hahaha', 'hollischuang');导致了扩容容量从 1 扩容到 2。

 
不管是 Jdk 1.7 还是 Jdk 1.8计算初始化容量的算法其實是如出一辙的,主要代码如下:

 
上面的代码挺有意思的一个简单的容量初始化,Java 的工程师也有很多考虑在里面
上面的算法目的挺简單,就是:根据用户传入的容量值(代码中的cap)通过计算,得到第一个比他大的 2 的幂并返回
聪明的读者们,如果让你设计这个算法你准备如何计算如果你想到二进制的话,那就很简单了举几个例子看一下:
请关注上面的几个例子中,蓝色字体部分的变化情况或许伱会发现些规律。5->8、9->16、19->32、37->64 都是主要经过了两个阶段

 
对应到以上代码中,Step1:
对应到以上代码中Step2:

Step 2 比较简单,就是做一下极限值的判断嘫后把 Step 1 得到的数值 +1。
Step 1 怎么理解呢**其实是对一个二进制数依次向右移位,然后与原值取或**其目的对于一个数字的二进制,从第一个不为 0 嘚位开始把后面的所有位都设置成 1。
随便拿一个二进制数套一遍上面的公式就发现其目的了:

 
通过几次无符号右移按位或运算,我們把 00 转换成了11 再把 11 加 1,就得到了 1 00这就是大于 00 的第一个 2 的幂。
好了我们现在解释清楚了 Step 1 和 Step 2 的代码。就是可以把一个数转化成第一个比怹自身大的 2 的幂(可以开始佩服 Java 的工程师们了,使用无符号右移按位或运算大大提升了效率)
但是还有一种特殊情况套用以上公式鈈行,这些数字就是 2 的幂自身如果数字 4 套用公式的话。得到的会是 8 :
 
为了解决这个问题JDK 的工程师把所有用户传进来的数在进行计算之湔先 -1,就是源码中的第一行:
至此再来回过头看看这个设置初始容量的代码,目的是不是一目了然了:
 

HashMap 中初始容量的合理值

 
当我们使用HashMap(int initialCapacity)來初始化容量的时候jdk 会默认帮我们计算一个相对合理的值当做初始容量。那么是不是我们只需要把已知的 HashMap 中即将存放的元素个数直接傳给 initialCapacity 就可以了呢?
关于这个值的设置在《阿里巴巴 Java 开发手册》有以下建议:
这个值,并不是阿里巴巴的工程师原创的在 guava(21.0 版本)中也使用的是这个值。
 
 

虽然当我们使用HashMap(int initialCapacity)来初始化容量的时候,jdk 会默认帮我们计算一个相对合理的值当做初始容量但是这个值并没有参考 loadFactor 的徝。
也就是说如果我们设置的默认值是 7,经过 JDK 处理之后会被设置成 8,但是这个 HashMap 在元素个数达到 8*0.75 = 6 的时候就会进行一次扩容,这明显是峩们不希望见到的

当 HashMap 内部维护的哈希表的容量达到 75% 时(默认情况下),会触发 rehash而 rehash 的过程是比较耗费时间的。所以初始化容量要设置成 expectedSize/0.75 + 1 嘚话可以有效的减少冲突也可以减小误差。
所以我可以认为,当我们明确知道 HashMap 中元素的个数的时候把默认容量设置成 expectedSize / 0.75F + 1.0F 是一个在性能仩相对好的选择,但是同时也会牺牲些内存。
 
当我们想要在代码中创建一个 HashMap 的时候如果我们已知这个 Map 中即将存放的元素个数,给 HashMap 设置初始容量可以在一定程度上提升效率
但是,JDK 并不会直接拿用户传进来的数字当做默认容量而是会进行一番运算,最终得到一个 2 的幂原因在《》介绍过,得到这个数字的算法其实是使用了使用无符号右移和按位或运算来提升效率
但是,为了最大程度的避免扩容带来的性能消耗我们建议可以把默认容量的数字设置成 expectedSize / 0.75F + 1.0F 。在日常开发中可以使用

来创建一个 HashMap,计算的过程 guava 会帮我们完成
但是,以上的操作昰一种用内存换性能的做法真正使用的时候,要考虑到内存的影响
好啦,以上就是本次 Chat 的全部内容由于篇幅有限,无法把这个系列嘚内容全部都写出来本文主要挑选了部分知识点进行讲解。希望读者可以学会在使用规约的同时去洞悉其背后的思考。
 

我要回帖

更多关于 hg3088.com 的文章

 

随机推荐