驳《阿里「Java开发手册」中的1个bug》?

这是我的第 211 期分享

作者 | 王磊

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

转载请联系授权(微信ID:GG_Stone)

前两天写了一篇关于《阿里Java开发手册中的 1 个bug》的文章,评论区有点炸锅了,基本分为两派,支持老王的和质疑老王的。

首先来说,无论是那一方,我都真诚的感谢你们。特别是「二师兄」,本来是打算周五晚上好好休息一下的(周五晚上发布的文章),结果因为和我讨论这个问题,一直搞到晚上 12 点左右,可以看出,他对技术的那份痴迷。这一点我们是一样的,和阅读本文的你一样,我们属于一类人,一类对技术无限痴迷的人


对与错的意义

其实在准备发这篇文章时,已经预料到这种局面了,当你提出质疑时,无论对错,一定有相反的声音,因为别人也有质疑的权利,而此刻你要做的,就是尽量保持冷静,用客观的态度去理解和验证这些问题。而这些问题(不同的声音)终将成为一笔宝贵的财富,因为你在这个验证的过程中一定会有所收获。

同时我也希望我的理解是错的,因为和大家一样,也是阿里《Java开发手册》的忠实“信徒”,只是意外的窥见了“不同”,然后顺手把自己的思路和成果分享给了大家。

但我也相信,任何“权威”都有犯错的可能,老祖宗曾告诉过我们“人非圣贤孰能无过”。我倒不是非要纠结谁对谁错,相反我认为一味的追求谁对谁错是件非常幼稚的事情,只有小孩子才这样做,我们要做的是通过辩论这件事的“对与错”,学习到更多的知识,帮助我们更好的成长,这才是我今天这篇文章诞生的意义。

乔布斯曾说过:我最喜欢和聪明人一起工作,因为完全不用顾忌他们的尊严。我倒不是聪明人,但我知道任何一件“错事”的背后,一定有它的价值。因此我不怕被“打脸”,如果想要快速成长的话,我劝你也要这样。

好了,就聊这么多,接下来咱们进入今天正题。

反对的声音

持不同看法的朋友的主要观点有以下这些:



我把这些意见整理了一下,其实说的是一件事,我们先来看原文的内容。

在《Java开发手册》泰山版(最新版)的第二章第三小节的第 4 条规范中指出:

【强制】在日志输出时,字符串变量之间的拼接使用占位符的方式。

说明:因为 String 字符串的拼接会使用 StringBuilder 的 append() 方式,有一定的性能损耗。使用占位符仅 是替换动作,可以有效提升性能

正例:logger.debug("Processing trade with id: {} and symbol: {}", id, symbol);

反对者(注意这个“反对者”不是贬义词,而是为了更好的区分角色)的意思是这样的:

使用占位符会先判断日志的输出级别再决定是否要进行拼接输出,而直接使用 StringBuilder 的方式会先进行拼接再进行判断,这样的话,当日志级别设置的比较高时,因为 StringBuilder 是先拼接再判断的,因此造成系统资源的浪费,所以使用占位符的方式比 StringBuilder 的方式性能要高。

咱先放下反对者说的这个含义在阿里《Java开发手册》中是否有体现,因为我确实没有看出来,咱们先顺着这个思路来证实一下这个结论是否正确。

性能评测

还是老规矩,咱们用数据和代码说话,为了测试 JMH(测试工具)能和 Spring Boot 很好的结合,首先我们要做的就是先测试一下日志输出级别设置,是否能在 JMH 的测试代码中生效。

那么接下来我们先来编写「日志级别设置」的测试代码:

import lombok.extern.slf4j.Slf4j;
import org.openjdk.jmh.annotations.*;
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 org.springframework.boot.SpringApplication;import java.util.concurrent.TimeUnit;@BenchmarkMode(Mode.AverageTime) // 测试完成时间
@OutputTimeUnit(TimeUnit.NANOSECONDS)
@Warmup(iterations = 2, time = 2, timeUnit = TimeUnit.SECONDS) // 预热 2 轮,每次 2s
@Measurement(iterations = 5, time = 3, timeUnit = TimeUnit.SECONDS) // 测试 5 轮,每次 3s
@Fork(1) // fork 1 个线程
@State(Scope.Thread) // 每个测试线程一个实例
@Slf4j
public class LogPrintAmend {public static void main(String[] args) throws RunnerException {// 启动基准测试Options opt = new OptionsBuilder().include(LogPrintAmend.class.getName() + ".*") // 要导入的测试类.build();new Runner(opt).run(); // 执行测试}@Setuppublic void init() {// 启动 spring bootSpringApplication.run(DemoApplication.class);}@Benchmarkpublic void logPrint() {log.debug("show debug");log.info("show info");log.error("show error");}
}

在测试代码中,我们使用了 3 个级别的日志输出指令:debug 级别、 info 级别和 error 级别。

然后我们再在配置文件(application.properties)中的设置日志的输出级别,配置如下:

logging.level.root=info

可以看出我们把所有的日志输出级别设置成了 info 级别,然后我们执行以上程序,执行结果如下:


从上图中我们可以看出,日志只输出了 info 和 error 级别,也就是说我们设置的日志输出级别生效了,为了保证万无一失,我们再把日志的输出级别降为 debug 级别,测试的结果如下图所示:


从上面的结果可以看出,我们设置的日志级别没有任何问题,也就是说,JMH 框架可以很好的搭配 SpringBoot 来使用

小贴士,日志的等级权重为:TRACE < DEBUG < INFO < WARN < ERROR < FATAL

有了上面日志等级的设置基础之后,我们来测试一下,如果先拼接字符串再判断输出的性能和占位符的性能评测结果,完整的测试代码如下:

import lombok.extern.slf4j.Slf4j;
import org.openjdk.jmh.annotations.*;
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 org.springframework.boot.SpringApplication;import java.util.concurrent.TimeUnit;@BenchmarkMode(Mode.AverageTime) // 测试完成时间
@OutputTimeUnit(TimeUnit.NANOSECONDS)
@Warmup(iterations = 2, time = 2, timeUnit = TimeUnit.SECONDS) // 预热 2 轮,每次 2s
@Measurement(iterations = 5, time = 3, timeUnit = TimeUnit.SECONDS) // 测试 5 轮,每次 3s
@Fork(1) // fork 1 个线程
@State(Scope.Thread) // 每个测试线程一个实例
@Slf4j
public class LogPrintAmend {private final static int MAX_FOR_COUNT = 100; // for 循环次数public static void main(String[] args) throws RunnerException {// 启动基准测试Options opt = new OptionsBuilder().include(LogPrintAmend.class.getName() + ".*") // 要导入的测试类.build();new Runner(opt).run(); // 执行测试}@Setuppublic void init() {SpringApplication.run(DemoApplication.class);}@Benchmarkpublic void appendLogPrint() {for (int i = 0; i < MAX_FOR_COUNT; i++) { // 循环的意图是为了放大性能测试效果// 先拼接StringBuilder sb = new StringBuilder();sb.append("Hello, ");sb.append("Java");sb.append(".");sb.append("Hello, ");sb.append("Redis");sb.append(".");sb.append("Hello, ");sb.append("MySQL");sb.append(".");// 再判断if (log.isInfoEnabled()) {log.info(sb.toString());}}}@Benchmarkpublic void logPrint() {for (int i = 0; i < MAX_FOR_COUNT; i++) { // 循环的意图是为了放大性能测试效果log.info("Hello, {}.Hello, {}.Hello, {}.", "Java", "Redis", "MySQL");}}
}

可以看出代码中使用了 info 的日志数据级别,那么此时我们再将配置文件中的日志级别设置为大于 info 的 error 级别,然后执行以上代码,测试结果如下:


哇,测试结果真令人满意。从上面的结果可以看出使用占位符的方式的性能,真的比 StringBuilder 的方式高很多,这就说明阿里的《Java开发手册》说的没问题喽。

反转

但事情并没有那么简单,就比如你正在路上走着,迎面而来了一个自行车,眼看就要撞到你了,此时你会怎么做?毫无疑问你会下意识的躲开。

那么对于上面的那个评测也是一样,为什么要在字符串拼接之后再进行判断呢?

如果编程已经是你的一份正式职业,那么先判断再拼接字符串是最基础的职业技能要求,这和你会下意识的躲开迎面相撞的自行车的道理是一样的,在你完全有能力规避问题的时候,一定是先规避问题,再进行其他操作的,否则在团队 review 代码的时候或者月底裁员的时候时,你一定是首选的“受害”对象了。因为像这么简单的(错误)问题,只有刚入门的新手才可能会出现。

那么按照一个程序最基本的要求,我们应该这样写代码:

import lombok.extern.slf4j.Slf4j;
import org.openjdk.jmh.annotations.*;
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 org.springframework.boot.SpringApplication;import java.util.concurrent.TimeUnit;@BenchmarkMode(Mode.AverageTime) // 测试完成时间
@OutputTimeUnit(TimeUnit.NANOSECONDS)
@Warmup(iterations = 2, time = 2, timeUnit = TimeUnit.SECONDS) // 预热 2 轮,每次 2s
@Measurement(iterations = 5, time = 3, timeUnit = TimeUnit.SECONDS) // 测试 5 轮,每次 3s
@Fork(1) // fork 1 个线程
@State(Scope.Thread) // 每个测试线程一个实例
@Slf4j
public class LogPrintAmend {private final static int MAX_FOR_COUNT = 100; // for 循环次数public static void main(String[] args) throws RunnerException {// 启动基准测试Options opt = new OptionsBuilder().include(LogPrintAmend.class.getName() + ".*") // 要导入的测试类.build();new Runner(opt).run(); // 执行测试}@Setuppublic void init() {SpringApplication.run(DemoApplication.class);}@Benchmarkpublic void appendLogPrint() {for (int i = 0; i < MAX_FOR_COUNT; i++) { // 循环的意图是为了放大性能测试效果// 再判断if (log.isInfoEnabled()) {StringBuilder sb = new StringBuilder();sb.append("Hello, ");sb.append("Java");sb.append(".");sb.append("Hello, ");sb.append("Redis");sb.append(".");sb.append("Hello, ");sb.append("MySQL");sb.append(".");log.info(sb.toString());}}}@Benchmarkpublic void logPrint() {for (int i = 0; i < MAX_FOR_COUNT; i++) { // 循环的意图是为了放大性能测试效果log.info("Hello, {}.Hello, {}.Hello, {}.", "Java", "Redis", "MySQL");}}
}

甚至是把 if 判断提到 for 循环外,但本文的 for 不代表具体的业务,而是为了更好的放大测试效果而写的代码,因此我们会把判断写到 for 循环内。

那么此时我们再来执行测试的代码,执行结果如下图所示:


从上述结果可以看出,使用先判断再拼接字符串的方式还是要比使用占位符的方式性能要高。

那么,我们依然没有办法证明阿里《Java开发手册》中的占位符性能高的结论

所以我依旧保持我的看法,使用占位符而非字符串拼接,主要可以保证代码的优雅性,可以在代码中少些一些逻辑判断,但这样写和性能无关

扩展知识:格式化日志

在上面的评测过程中,我们发现日志的输出格式非常“乱”,那有没有办法可以格式化日志呢?

答案是:有的,默认日志的输出效果如下:


格式化日志可以通过配置 Spring Boot 中的 logging.pattern.console 选项实现的,配置示例如下:

logging.pattern.console=%d | %msg %n

日志的输出结果如下:


可以看出,格式化日志之后,内容简洁多了,但千万不能因为简洁,而遗漏输出关键性的调试信息。

总结

本文我们测试了读者提出质疑的字符串拼接和占位符的性能评测,发现占位符方式性能高的观点依然无从考证,所以我们的基本看法还是,使用占位符的方式更加优雅,可以通过更少的代码实现更多的功能;至于性能方面,只能说还不错,但不能说很优秀。在文章的最后我们讲了 Spring Boot 日志格式化的知识,希望本文可以切实的帮助到你,也欢迎你在评论区留言和我互动。

最后的话原创不易,都看到这了,点个「在看」再走呗,这是对我最大的支持与鼓励,谢谢你!往期推荐

阿里《Java开发手册》中的 1 个bug!

阿里新版《Java 开发手册(泰山版)》内容解读(附下载地址)

关注下方二维码,每一天都有干货!

点亮“在看”,助我写出更多好文!

本文来自互联网用户投稿,该文观点仅代表作者本人,不代表本站立场。本站仅提供信息存储空间服务,不拥有所有权,不承担相关法律责任。如若转载,请注明出处:http://www.mzph.cn/news/546082.shtml

如若内容造成侵权/违法违规/事实不符,请联系多彩编程网进行投诉反馈email:809451989@qq.com,一经查实,立即删除!

相关文章

轻松学算法的秘密!可视化算法网站汇总!(附动图)

对于「算法」的第一印象&#xff0c;我相信大部分人都是一样的&#xff0c;就是一个“难”字了得。而我比较特殊&#xff0c;我的第一印象、第二印象以至第 N 印象都觉得很难&#xff0c;所以为了更好的学习和理解算法&#xff0c;我千金一掷一下买了一堆的算法书&#xff0c;有…

链表竟然比数组慢了1000多倍?(动图+性能评测)

这是我的第 215 期分享作者 | 王磊来源 | Java中文社群&#xff08;ID&#xff1a;javacn666&#xff09;转载请联系授权&#xff08;微信ID&#xff1a;GG_Stone&#xff09;数组和链表是程序中常用的两种数据结构&#xff0c;也是面试中常考的面试题之一。然而对于很多人来说…

win7安装python

2019独角兽企业重金招聘Python工程师标准>>> 官网下载 python 安装包&#xff0c;一个 .msi 文件&#xff0c;双击安装 右键[计算机]-属性-高级系统设置-环境变量&#xff0c;在[系统变量]中找到 Path 变量&#xff0c;双击&#xff0c;在值后面添加 python 的安装路…

漫画:Java如何实现热更新?

Arthas&#xff08;阿尔萨斯&#xff09;是 Alibaba 开源的一款 Java 诊断工具&#xff0c;使用它我们可以监控和排查 Java 程序&#xff0c;然而它还提供了非常实用的 Java 热更新功能。所谓的 Java 热更新是指在不重启项目的情况下实现代码的更新与替换。使用它可以实现不停机…

自动化运维工具Saltstack详细介绍

Saltstack是一个新的基础设施管理工具。目前处于快速发展阶段&#xff0c;可以看做是pssh弱化的Puppet的组合。间接的反映出了saltstack的两大功能&#xff1a;远程执行和配置管理。Saltstack使用Python开发&#xff0c;是一个非常简单易用和轻量级的管理工具。由Master和Minio…

为什么建议你使用枚举?

枚举是 JDK 1.5 新增的数据类型&#xff0c;使用枚举我们可以很好的描述一些特定的业务场景&#xff0c;比如一年中的春、夏、秋、冬&#xff0c;还有每周的周一到周天&#xff0c;还有各种颜色&#xff0c;以及可以用它来描述一些状态信息&#xff0c;比如错误码等。枚举类型不…

数据结构树二叉树计算节点_查找二叉树中叶节点的数量 数据结构

数据结构树二叉树计算节点Algorithm: 算法&#xff1a; One of the popular traversal techniques to solve this kind of problems is level order tree traversal (Read: Level Order Traversal on a Binary Tree) where we use the concept of BFS. 解决此类问题的一种流行…

重磅!阿里推出国产开源JDK!

简介Alibaba Dragonwell 是一款免费的, 生产就绪型Open JDK 发行版&#xff0c;提供长期支持&#xff0c;包括性能增强和安全修复。阿里巴巴拥有最丰富的Java应用场景&#xff0c;覆盖电商&#xff0c;金融&#xff0c;物流等众多领域&#xff0c;世界上最大的Java用户之一。Al…

安装TPCC-MySQL报错

2019独角兽企业重金招聘Python工程师标准>>> 安装TPCC-MySQL做压力测试&#xff0c;由于TPCC-MySQL是bzr工具进行版本控制的&#xff0c;所以要先安装bzr [rootmha_backup /root] #rpm -Uvh http://dl.fedoraproject.org/pub/epel/5/i386/epel-release-5-4.noarch.r…

自由职业的前半年,我是如何度过的?

生活中所受的苦&#xff0c;终会以一种形式回归。我是一个后知后觉的人&#xff0c;从 2009 年毕业到现在&#xff0c;已经有 11 年的光景了&#xff0c;参加工作的前几年我从没想过要快速的提升自己的技能&#xff0c;对待工作也没有全力以赴&#xff0c;这样的迷茫和随大流的…

Eucalyptus常用查询命令

前言&#xff1a; Elastic Utility Computing Architecture for Linking Your Programs To Useful Systems &#xff08;Eucalyptus&#xff09; 是一种开源的软件基础结构&#xff0c;用来通过计算集群或工作站群实现弹性的、实用的云计算。它最初是美国加利福尼亚大学 Santa …

调整灰度图像的大小,而无需在Python中使用任何内置函数

In this program, we will be using two functions of OpenCV-python (cv2) module. Lets see their syntax and descriptions first. 在此程序中&#xff0c;我们将使用OpenCV-python(cv2)模块的两个功能。 首先让我们看看它们的语法和说明。 1) imread():It takes an absolu…

漫画:对象是如何被找到的?句柄 OR 直接指针?

小贴士&#xff1a;想要使用并定位 Java 对象&#xff0c;就要用到 Java 虚拟机栈&#xff08;Java Virtual Machine Stack&#xff09;&#xff0c;它描述的是 Java 方法执行的线程内存模型&#xff1a;每个方法被执行的时候&#xff0c;Java 虚拟机都会同步创建一个栈帧&…

IDEA 不为人知的 5 个骚技巧!真香!

工欲善其事&#xff0c;必先利其器&#xff0c;磊哥最近发现了几个特别棒的 IDEA“骚”技巧&#xff0c;已经迫不及待的想要分享给你了&#xff0c;快上车...1.快速补全行末分号使用快捷键 Shfit Ctrl Enter 轻松实现。2.自带的 HTTP 请求工具IDEA 自带了 HTTP 的测试工具&am…

漫画:Integer 竟然有 4 种比较方法?

代码测试public class IntegerTest {public static void main(String[] args) {Integer i1 127;Integer i2 127;System.out.println(i1 i2);Integer i3 128;Integer i4 128;System.out.println(i3 i4);} }以上代码的执行结果为&#xff1a;truefalse首先&#xff0c;当我…

6种快速统计代码执行时间的方法,真香!(史上最全)

我们在日常开发中经常需要测试一些代码的执行时间&#xff0c;但又不想使用向 JMH&#xff08;Java Microbenchmark Harness&#xff0c;Java 微基准测试套件&#xff09;这么重的测试框架&#xff0c;所以本文就汇总了一些 Java 中比较常用的执行时间统计方法&#xff0c;总共…

连夜整理了几个开源项目,毕设/练手/私活一条龙!

一直以来&#xff0c;总有小伙伴问说&#xff1a;诶&#xff0c;有没有什么好的项目推荐啊&#xff0c;想参考使用。一般用途无非如下几种情况&#xff1a;自学练手&#xff1a;从书本和博客的理论学习&#xff0c;过渡到实践练手吸收项目经验&#xff0c;找工作写简历时能参考…

MPI编程简单介绍

第三章 MPI编程 3.1 MPI简单介绍 多线程是一种便捷的模型&#xff0c;当中每一个线程都能够訪问其他线程的存储空间。因此&#xff0c;这样的模型仅仅能在共享存储系统之间移植。一般来讲&#xff0c;并行机不一定在各处理器之间共享存储&#xff0c;当面向非共享存储系统开发…

最简单的6种防止数据重复提交的方法!(干货)

有位朋友&#xff0c;某天突然问磊哥&#xff1a;在 Java 中&#xff0c;防止重复提交最简单的方案是什么&#xff1f;这句话中包含了两个关键信息&#xff0c;第一&#xff1a;防止重复提交&#xff1b;第二&#xff1a;最简单。于是磊哥问他&#xff0c;是单机环境还是分布式…