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

今天给大家带来的是关于 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(); // 执行测试
} @Benchmark
public 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;
} @Benchmark
public 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_0
1: invokespecial #1 // Method java/lang/Object."<init>":()V
4: return public static void main(java.lang.String[]);
Code:
0: iconst_0
1: istore_1
2: new #2 // class java/lang/Exception
5: dup
6: ldc #3 // String new Exception
8: invokespecial #4 // Method java/lang/Exception."<init>":(Ljava/lang/String;)V
11: athrow
12: astore_1
13: aload_1
14: invokevirtual #5 // Method java/lang/Exception.printStackTrace:()V
17: return
Exception table:
from to target type
0 12 12 Class java/lang/Exception
}

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

Exception table:
from to target type
0 12 12 Class java/lang/Exception

参数说明:

  • from:表示 try-catch 的开始地址;
  • to:表示 try-catch 的结束地址;
  • target:表示异常的处理起始位;
  • type:表示异常类名称。

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

业务情况分析

虽然 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 应该存放的位置,而非性能考虑

关注公众号「Java中文社群」回复“干货”,获取 50 篇原创干货 Top 榜

啪啪,打脸了!领导说:try-catch必须放在循环体外!的更多相关文章

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

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

  2. 日报 18/07/15 Java 性能优化

    尽量指定类和方法的final修饰符 带有final修饰符的类是不可派生的 在java核心api中 有许多应用final的例子 例如 java.lang.string整个类都是final的 为类指定fi ...

  3. JDBC的PreparedStatement启动事务使用批处理executeBatch()

    JDBC使用MySQL处理大数据的时候,自然而然的想到要使用批处理, 普通的执行过程是:每处理一条数据,就访问一次数据库: 而批处理是:累积到一定数量,再一次性提交到数据库,减少了与数据库的交互次数, ...

  4. j2ee项目Java代码性能优化要点(抄书)

    亚信联创科技出版的. 1.与log4j有关的性能问题 Logger对象的标准定义方式: private static transient Logger log=Logger.getLogger(cre ...

  5. JDBC MYSQL 学习笔记(一) JDBC 基本使用

    1.JDBC简单介绍 SUN公司为了简化.统一对数据库的操作,定义了一套Java操作数据库的规范.称之为JDBC. JDBC全称为:Java Data Base Connectivity(java数据 ...

  6. JAVA JDBC大数据量导入Mysql

    转自https://blog.csdn.net/q6834850/article/details/73726707?tdsourcetag=s_pctim_aiomsg 采用JDBC批处理(开启事务. ...

  7. python 字符串拼接效率打脸帖

    https://www.cnblogs.com/chenjingyi/p/5741901.html 这篇博客写的好,字符串并不是+ 效率就一定比 "%" % ('a') 就低. 按 ...

  8. C++异常处理:try,catch,throw,finally的用法

    写在前面 所谓异常处理,即让一个程序运行时遇到自己无法处理的错误时抛出一个异常,希望调用者可以发现处理问题. 异常处理的基本思想是简化程序的错误代码,为程序键壮性提供一个标准检测机制. 也许我们已经使 ...

  9. C++异常处理: try,catch,throw,finally的用法

    写在前面 所谓异常处理,即让一个程序运行时遇到自己无法处理的错误时抛出一个异常,希望调用者可以发现处理问题. 异常处理的基本思想是简化程序的错误代码,为程序键壮性提供一个标准检测机制. 也许我们已经使 ...

随机推荐

  1. C - 小明系列故事――捉迷藏 HDU - 4528 bfs +状压 旅游-- 最短路+状压

    C - 小明系列故事――捉迷藏 HDU - 4528 这个题目看了一下题解,感觉没有很难,应该是可以自己敲出来的,感觉自己好蠢... 这个是一个bfs 用bfs就很好写了,首先可以预处理出大明和二明能 ...

  2. Uncaught DOMException: Failed to set the 'value' property on 'HTMLInputElement': This input element accepts a filename, which may only be programmatically set to the empty string.

    今天上传图片遇到这个报错 百度了下,网上说是input标签type=file所以导致的问题,可是我的type=hidden 解决办法: 把上面的代码改成如下问题就解决了

  3. kafka简介及集群部署

    消息队列概念:(Message queue): “消息”是在两台计算机间传送的数据单位.消息可以非常简单,例如只包含文本字符串:也可以更复杂,可能包含嵌入对象. “消息队列”是在消息的传输过程中保存消 ...

  4. 基于BasicRF点对点无线开发基础知识

    BasicRF点对点概述 BasicRF软件包有四大部分: <1> 硬件层:Hardware Layer. <2> 硬件抽象层:Haware Abstraction Layer ...

  5. Linux共享库简单总结

    库 静态库 编译的二进制会重新包含一份静态库的副本 共享库 编译 gcc -shared -o file.c -fPIC 链接 ld ld-linux.so.2 可执行程序–>动态依赖表 流程: ...

  6. Centos7 网卡桥接

     一.在centos7主机创建用于虚拟化的网桥 1)增加 /etc/sysconfig/network-scripts/ifcfg-br0 DEVICE=br0BOOTPROTO=staticONBO ...

  7. Vue3语法快速入门以及写一个倒计时组件

    Vue3写一个倒计时组件 vue3 beta版本发布已有一段时间了,文档也大概看了一下,不过对于学一门技术,最好的方法还是实战,于是找了一个比较简单的组件用vue3来实现,参考的是vant的count ...

  8. Mysql常用sql语句(17)- left/right join 外连接

    测试必备的Mysql常用sql语句系列 https://www.cnblogs.com/poloyy/category/1683347.html 前言 外连接分为两种:left join.right ...

  9. [hdu4888]最大流,判断最大流唯一性

    题意:给一个n*m的矩形,往每个格子填0-k的数字,使得对第i行和为row[i],第i列和为col[i],问是否存在方案,方案是否唯一,如果方案唯一则输出具体方案. 思路:首先根据问题提取对象,行.列 ...

  10. DIV+CSS布局的优势和弊端

    DIV+CSS的优势1.符合W3C标准.这保证您的网站不会因为将来网络应用的升级而被淘汰.2.对浏览者和浏览器更具亲和力.由于CSS富含丰富的样式,使页面更加灵活性,它可以根据不同的浏览器,而达到显示 ...