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

今天给大家带来的是关于 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. spring学习笔记(二)spring中的事件及多线程

    我们知道,在实际开发中为了解耦,或者提高用户体验,都会采用到异步的方式.这里举个简单的例子,在用户注册的sh时候,一般我们都会要求手机验证码验证,邮箱验证,而这都依赖于第三方.这种情况下,我们一般会通 ...

  2. JMeter-SHA256加密算法接口测试

    前言 我们已经学过了如何利用python脚本对SHA256加密接口进行测试,还没学会的小伙伴们可以看回我写的随笔Python-SHA256加密算法接口测试,那么,对于SHA256加密算法接口,我们如何 ...

  3. python语法学习第六天--集合

    集合(set)是一个无序的不重复元素序列. 可以使用大括号 { } 或者 set() 函数创建集合,注意:创建一个空集合必须用 set() 而不是 { },因为 { } 是用来创建一个空字典. 创建格 ...

  4. ASP.NET Core on K8S学习之旅(13)Ocelot API网关接入

    本篇已加入<.NET Core on K8S学习实践系列文章索引>,可以点击查看更多容器化技术相关系列文章. 上一篇介绍了Ingress的基本概念和Nginx Ingress的基本配置和使 ...

  5. 最简单的git 用法

    步骤 在机器上ssh-keygen 然后把id_rsa.pub 复制到csdn 的公钥上去. git clone git@code.csdn.net:aca_jingru/zabbix.git 如果这 ...

  6. 关于RAID小结

    独立硬盘冗余阵列(RAID, Redundant Array of Independent Disks),旧称廉价磁盘冗余阵列(Redundant Array of Inexpensive Disks ...

  7. 关于mysql的metadata lock

    昨天晚上上线,却发现一个ddl语句长时间没有生效 查processlist, 发现包括ddl语句在内的众多查询提示 “Waiting for table metadata lock” 唯一没有该提示的 ...

  8. 如何在HTML5中使用SVG

    复制而来---原地址http://www.php100.com/html/webkaifa/HTML5/2012/0731/10776.html SVG 即 Scalable Vector Graph ...

  9. mysql基本操作汇总

    1.数据库操作 (1)创建数据库 CREATE DATABASE <数据库名>; 例子: CREATE DATABASE IF NOT EXISTS RUNOOB DEFAULT CHAR ...

  10. Fundamental ES6 Part-I

    Exercise-01 with Solution Write a JavaScript program to compare two objects to determine if the firs ...