这是我的第 206 期分享

作者 | 王磊

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

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

哈喽,亲爱的小伙伴们,技术学磊哥,进步没得说!欢迎来到新一期的性能解读系列,我是磊哥。

今天给大家带来的是关于 try-catch 应该放在循环体外,还是放在循环体内的文章,我们将从性能业务场景分析这两个方面来回答此问题。

很多人对 try-catch 有一定的误解,比如我们经常会把它(try-catch)和“低性能”直接画上等号,但对 try-catch 的本质(是什么)却缺少着最基础的了解,因此我们也会在本篇中对 try-catch 的本质进行相关的探索

小贴士:我会尽量用代码和评测结果来证明问题,但由于本身认知的局限,如有不当之处,请读者朋友们在评论区指出。

性能评测

话不多说,我们直接来开始今天的测试,本文我们依旧使用 Oracle 官方提供的 JMH(Java Microbenchmark Harness,JAVA 微基准测试套件)来进行测试。

首先在 pom.xml 文件中添加 JMH 框架,配置如下:

<!-- https://mvnrepository.com/artifact/org.openjdk.jmh/jmh-core -->
<dependency><groupId>org.openjdk.jmh</groupId><artifactId>jmh-core</artifactId><version>{version}</version>
</dependency>

完整测试代码如下:

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 java.util.concurrent.TimeUnit;/*** try - catch 性能测试*/
@BenchmarkMode(Mode.AverageTime) // 测试完成时间
@OutputTimeUnit(TimeUnit.NANOSECONDS)
@Warmup(iterations = 1, time = 1, timeUnit = TimeUnit.SECONDS) // 预热 1 轮,每次 1s
@Measurement(iterations = 5, time = 5, timeUnit = TimeUnit.SECONDS) // 测试 5 轮,每次 3s
@Fork(1) // fork 1 个线程
@State(Scope.Benchmark)
@Threads(100)
public class TryCatchPerformanceTest {private static final int forSize = 1000; // 循环次数public static void main(String[] args) throws RunnerException {// 启动基准测试Options opt = new OptionsBuilder().include(TryCatchPerformanceTest.class.getSimpleName()) // 要导入的测试类.build();new Runner(opt).run(); // 执行测试}@Benchmarkpublic int innerForeach() {int count = 0;for (int i = 0; i < forSize; i++) {try {if (i == forSize) {throw new Exception("new Exception");}count++;} catch (Exception e) {e.printStackTrace();}}return count;}@Benchmarkpublic int outerForeach() {int count = 0;try {for (int i = 0; i < forSize; i++) {if (i == forSize) {throw new Exception("new Exception");}count++;}} catch (Exception e) {e.printStackTrace();}return count;}
}

以上代码的测试结果为:


从以上结果可以看出,程序在循环 1000 次的情况下,单次平均执行时间为:

  • 循环内包含 try-catch 的平均执行时间是 635 纳秒 ±75 纳秒,也就是 635 纳秒上下误差是 75 纳秒;

  • 循环外包含 try-catch 的平均执行时间是 630 纳秒,上下误差 38 纳秒。

也就是说,在没有发生异常的情况下,除去误差值,我们得到的结论是:try-catch 无论是在 for 循环内还是  for 循环外,它们的性能相同,几乎没有任何差别

try-catch的本质

要理解 try-catch 的性能问题,必须从它的字节码开始分析,只有这样我能才能知道 try-catch 的本质到底是什么,以及它是如何执行的。

此时我们写一个最简单的 try-catch 代码:

public class AppTest {public static void main(String[] args) {try {int count = 0;throw new Exception("new Exception");} catch (Exception e) {e.printStackTrace();}}
}

然后使用 javac 生成字节码之后,再使用 javap -c AppTest 的命令来查看字节码文件:

➜ javap -c AppTest
警告: 二进制文件AppTest包含com.example.AppTest
Compiled from "AppTest.java"
public class com.example.AppTest {public com.example.AppTest();Code:0: aload_01: invokespecial #1                  // Method java/lang/Object."<init>":()V4: returnpublic static void main(java.lang.String[]);Code:0: iconst_01: istore_12: new           #2                  // class java/lang/Exception5: dup6: ldc           #3                  // String new Exception8: invokespecial #4                  // Method java/lang/Exception."<init>":(Ljava/lang/String;)V11: athrow12: astore_113: aload_114: invokevirtual #5                  // Method java/lang/Exception.printStackTrace:()V17: returnException table:from    to  target type0    12    12   Class java/lang/Exception
}

从以上字节码中可以看到有一个异常表:

Exception table:from    to  target type0    12    12   Class java/lang/Exception

参数说明:

  • from:表示 try-catch 的开始地址;

  • to:表示 try-catch 的结束地址;

  • target:表示异常的处理起始位;

  • type:表示异常类名称。

从字节码指令可以看出,当代码运行时出错时,会先判断出错数据是否在 from 到 to 的范围内,如果是则从 target 标志位往下执行,如果没有出错,直接 goto 到 return。也就是说,如果代码不出错的话,性能几乎是不受影响的,和正常的代码的执行逻辑是一样的。

业务情况分析

虽然 try-catch 在循环体内还是循环体外的性能是类似的,但是它们所代码的业务含义却完全不同,例如以下代码:

public class AppTest {public static void main(String[] args) {System.out.println("循环内的执行结果:" + innerForeach());System.out.println("循环外的执行结果:" + outerForeach());}// 方法一public static int innerForeach() {int count = 0;for (int i = 0; i < 6; i++) {try {if (i == 3) {throw new Exception("new Exception");}count++;} catch (Exception e) {e.printStackTrace();}}return count;}// 方法二public static int outerForeach() {int count = 0;try {for (int i = 0; i < 6; i++) {if (i == 3) {throw new Exception("new Exception");}count++;}} catch (Exception e) {e.printStackTrace();}return count;}
}

以上程序的执行结果为:

java.lang.Exception: new Exception

at com.example.AppTest.innerForeach(AppTest.java:15)

at com.example.AppTest.main(AppTest.java:5)

java.lang.Exception: new Exception

at com.example.AppTest.outerForeach(AppTest.java:31)

at com.example.AppTest.main(AppTest.java:6)

循环内的执行结果:5

循环外的执行结果:3

可以看出在循环体内的 try-catch 在发生异常之后,可以继续执行循环;而循环外的 try-catch 在发生异常之后会终止循环。

因此我们在决定 try-catch 究竟是应该放在循环内还是循环外,不取决于性能(因为性能几乎相同),而是应该取决于具体的业务场景

例如我们需要处理一批数据,而无论这组数据中有哪一个数据有问题,都不能影响请他组的正常执行,此时我们可以把 try-catch 放置在循环体内;而当我们需要计算一组数据的合计值时,只要有一组数据有误,我们就需要终止执行,并抛出异常,此时我们需要将 try-catch 放置在循环体外来执行。

总结

本文我们测试了 try-catch 放在循环体内和循环体外的性能,发现二者在循环很多次的情况下性能几乎是一致的。然后我们通过字节码分析,发现只有当发生异常时,才会对比异常表进行异常处理,而正常情况下则可以忽略 try-catch 的执行。但在循环体内还是循环体外使用 try-catch,对于程序的执行结果来说是完全不同的,因此我们应该从实际的业务出发,来决定到 try-catch 应该存放的位置,而非性能考虑

往期推荐

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

局部变量竟然比全局变量快 5 倍?

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

啪啪打脸!领导说:try-catch要放在循环体外!相关推荐

  1. dnf机器人猜数字奖励_DNF:周年庆策划啪啪打脸,工作人员也出错误,难道又是临时工的锅?...

    本来应该是很喜庆的一天,结果又遇见了策划啪啪打脸的事情,关键还连累玩家提心吊胆的.往年都是登录游戏送豪礼,今年策划就像搞点不一样,估计是认为今年送的"即时"史诗比较多吧.然后就很任 ...

  2. try catch在for循环外面还是里面

    一.实例展示 static void Main(string[] args) {//将异常写在循环外,出现异常循环终止try {Console.WriteLine("抛出异常不输出" ...

  3. 手机录音失真_“手机都能录音,还要啥录音笔”,四点原因让说这话的人啪啪打脸...

    相信很多人都有个疑问,现在智能手机都能录音了,为什么还是那么多人买录音笔,而且一些比较高端的录音笔价格还不算便宜,当真"人傻钱多"? 小编可以斩钉截铁的说,真不是这样!录音笔作为专 ...

  4. c语言立flag是什么意思,真香是什么意思 立flag不到二分钟啪啪打脸

    说起"真香"一词,可能大多数的人都一脸懵,不知道它是什么意思.但是当你在看剧的时候弹幕上你就会发现好多的人一直刷,而且经常用来说那种高冷的报道总裁男主,那么小编今天就来说说这个词的 ...

  5. 吐槽安卓系统体验差用户 这次啪啪啪打脸了

    自去年推出EMUI 5.0起,周围就一直有人给小编安利这个系统,据说EMUI5.0在设计.性能.智慧服务等方面均有大幅提升,是目前安卓阵容中最流畅的系统,还被用户称为史上最完美系统.在各种花式安利下, ...

  6. 啪啪打脸,国际互联网协会数据泄露

    数万名国际互联网协会成员的个人信息因配置错误而被曝光在互联网上.作为互联网世界相关标准的制定.推广的机构,以推动互联网的发展为己任,常发布行业最佳实践供企业参考,却也因为网络安全漏洞出现信息被泄露事件 ...

  7. 作为Lombok的信徒,我这次翻车了,啪啪打脸~

    相关阅读:一个90后员工猝死的全过程 作者:liuxuzxx来源:juejin.im/post/6881432532332576781 下面开始我们的正文.去年在某项目当中引入了 Lombok 插件, ...

  8. 为什么将iostream :: eof放在循环条件(即`while(!stream.eof())`)内?

    我刚刚在此答案中找到一条评论,说在循环条件下使用iostream::eof是"几乎肯定是错误的". 我通常使用while(cin>>n)类的东西-我猜它隐式地检查EOF ...

  9. 看故事学知识,这篇Java代理的文章妙啊!

    这是我的第 208 期分享 作者 | java金融 来源 | java金融(ID:java4299) 分享 | Java中文社群(ID:javacn666) 什么是代理 代理模式是常用的java设计模 ...

最新文章

  1. VScode快速一键生成html、vue、jsx、ajax、sass、docker等代码片段
  2. poj 3261 后缀数组 找反复出现k次的子串(子串能够重叠)
  3. Adobe Flash player 10 提示:Error#2044:未处理的IOErrorEvent. text=Error#2036:加载未完成 的解决方法
  4. cmake编译时遇到的问题解决
  5. 智能导航短信告警的一个逻辑处理
  6. internetreadfile读取数据长度为0_【完结】TensorFlow2.0 快速上手手册
  7. 教室信息管理系统mysql_教师信息管理系统(方式一:数据库为oracle数据库;方式二:存储在文件中)...
  8. OpenCV中的内存泄露问题(cvLoadImage,cvCloneImage)【转】
  9. 使用staatus和defaultStatus属性改变状态栏信息
  10. saltstack 模型与认证通信原理
  11. (五)基于matchTemplate的图像区域匹配
  12. 求出现重现次数最多的字母,如有多个反复的则都求出来
  13. 暴风影音5【版本:5.36.0428.1111】---给您影院般的“极致”体验
  14. 解决“更新pip版本竟将pip卸载了,提示No module named ‘pip‘”
  15. Java icetea_java – OpenJDK 8的IcedTea插件
  16. Python基础知识点回顾
  17. json_encode函数参数详解
  18. 修改 QQ 任务栏托盘区小图标和等级图标
  19. cesium分屏对比
  20. SCP,NFS,TFTP的初步认识

热门文章

  1. oracle sql last_value,図でイメージするOracle DatabaseのSQL全集 第3回 分析関数
  2. 百度之星初赛(A)——T5
  3. Android横竖屏切换View设置不同尺寸或等比例缩放的自定义View的onMeasure解决方案(2)...
  4. Vim常用按键操作学习
  5. 20141215胡思乱想
  6. 【JavaScript学习】JavaScript对象创建
  7. 【034】◀▶ 学习网站 问题解决
  8. OCP题库笔记1z0-052
  9. (转载)简洁、明晰!数据库设计三大范式应用实例剖析
  10. C#开发高亮语法编辑器(一)——TextBox ,RichTextBox