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


prtyaa
prtyaa 2023-12-31 22:39:59 63532
分类专栏: 资讯

今天给大家带来的是关于 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:表示异常类名称。

从字节码指令可以看出,当代码运行时出错时,会先判断出错数据是否在 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 应该存放的位置,而非性能考虑

网站声明:如果转载,请联系本站管理员。否则一切后果自行承担。

本文链接:https://www.xckfsq.com/news/show.html?id=33421
赞同 0
评论 0 条
prtyaaL1
粉丝 1 发表 2554 + 关注 私信
上周热门
银河麒麟添加网络打印机时,出现“client-error-not-possible”错误提示  1523
银河麒麟打印带有图像的文档时出错  1442
银河麒麟添加打印机时,出现“server-error-internal-error”  1233
统信操作系统各版本介绍  1153
统信桌面专业版【如何查询系统安装时间】  1149
统信桌面专业版【全盘安装UOS系统】介绍  1106
麒麟系统也能完整体验微信啦!  1060
统信【启动盘制作工具】使用介绍  713
统信桌面专业版【一个U盘做多个系统启动盘】的方法  656
信刻全自动档案蓝光光盘检测一体机  561
本周热议
我的信创开放社区兼职赚钱历程 40
今天你签到了吗? 27
如何玩转信创开放社区—从小白进阶到专家 15
信创开放社区邀请他人注册的具体步骤如下 15
方德桌面操作系统 14
我有15积分有什么用? 13
用抖音玩法闯信创开放社区——用平台宣传企业产品服务 13
如何让你先人一步获得悬赏问题信息?(创作者必看) 12
2024中国信创产业发展大会暨中国信息科技创新与应用博览会 9
中央国家机关政府采购中心:应当将CPU、操作系统符合安全可靠测评要求纳入采购需求 8

添加我为好友,拉您入交流群!

请使用微信扫一扫!