面试官:try-catch放在循环体内还是循环体外,哪种效率更高?

前言

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

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

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

性能评测

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

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

<!-- https://mvnrepository.com/artifact/org.openjdk.jmh/jmh-core -->

org.openjdk.jmh

jmh-core

{version}

完整测试代码如下:

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 //fork1个线程

@State(Scope.Benchmark)

@Threads

public class TryCatchPerformanceTest {

private static finalintforSize =1000;//循环次数

public static void main(String[] args) throws RunnerException {

//启动基准测试

Options opt = new OptionsBuilder()

.include(TryCatchPerformanceTest.class.getSimpleName()) // 要导入的测试类

.build();

new Runner(opt).run();//执行测试

}

@Benchmark

publicintinnerForeach() {

intcount =0;

for(inti =0; i < forSize; i++) {

try {

if(i == forSize) {

throw new Exception("new Exception");

}

count++;

} catch (Exception e) {

e.printStackTrace();

}

}

returncount;

}

@Benchmark

publicintouterForeach() {

intcount =0;

try {

for(inti =0; i < forSize; i++) {

if(i == forSize) {

throw new Exception("new Exception");

}

count++;

}

} catch (Exception e) {

e.printStackTrace();

}

returncount;

}

}

以上代码的测试结果为:

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

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

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

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

try-catch的本质

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

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

publicclassAppTest{

publicstaticvoidmain(String[] args){

try{

intcount =0;

thrownewException("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

01212Class java/lang/Exception

}

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

Exception table:

fromtotargettype

01212Classjava/lang/Exception

参数说明:

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

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

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

type:表示异常类名称。

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

业务情况分析

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

publicclassAppTest{

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

全部评论

相关推荐

逆流河上万仙退:如果是能有面试的话应该简历没啥问题 争取表现好一点然后到岗时间实习天数往长了说 先看看能不能有offer
点赞 评论 收藏
分享
mq2:我倒是觉得这种敞亮一点好。能接受就去不能就不去呗。 完了跟现在“正常”公司一样,hr说的天花乱坠,进去一看根本就是996核动力牛马,想走又没应届生身份了。岂不是更糟。
点赞 评论 收藏
分享
评论
点赞
收藏
分享

创作者周榜

更多
牛客网
牛客企业服务