啪啪打臉!領導說: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 應該存放的位置,而非效能考慮。
最後
歡迎大家關注和轉發文章,也歡迎大家關注我的公眾號:程式設計師麥冬,回覆“007”領取200頁的Java核心面試資料整理,每天都會分享java相關技術文章或行業資訊
相關文章
- 啪啪,打臉了!領導說:try-catch必須放在迴圈體外!
- 說服的要領
- OCR迴圈:說說遊戲中的挑戰與體驗遊戲
- JavaScript 事件迴圈(1) —— 從 setTimeout 說起JavaScript事件
- 迴圈開拓者:規模企業引領迴圈經濟深化發展
- for 迴圈與 while 迴圈While
- while迴圈 case迴圈While
- C語言——迴圈結構(for迴圈,while迴圈,do-while迴圈)C語言While
- CSS3提交按鈕等待打點迴圈效果CSSS3
- 曹工說Redis原始碼(6)-- redis server 主迴圈大體流程解析Redis原始碼Server
- 無限for迴圈(死迴圈)
- 解決NSTimer迴圈引用導致記憶體洩漏的六種方法記憶體
- while迴圈以及do while迴圈While
- 迴圈_推導式_繪製棋盤_函式函式
- JAVA多層迴圈裡面怎麼一個break直接跳出到最外層迴圈位置Java
- if迴圈
- 迴圈
- for迴圈
- if for迴圈
- For 迴圈
- 04流程控制 for迴圈,while迴圈While
- 迴圈引用導致的json序列化失敗JSON
- 他說遇到了迴圈匯入,但是我怎麼看我的程式碼都沒有迴圈匯入
- 結構體的使用 for迴圈使用方式結構體
- java常見語句之迴圈體Java
- 11C++迴圈結構-for迴圈(1)C++
- for迴圈、break和continue、二重迴圈
- 【基礎題】【for迴圈】二重迴圈
- 臉書David Marcus領導新的區塊鏈研究部門區塊鏈
- 從一個案例,細說瀏覽器的事件迴圈瀏覽器事件
- 為什麼 Go1.22 for 迴圈要支援整數範圍?Go
- 抬槓:寫個死迴圈,還要讓頁面正常跑
- Java迴圈Java
- javaScript for迴圈JavaScript
- 事件迴圈事件
- while迴圈While
- pythonfor迴圈Python
- 迴圈群