日韩性视频-久久久蜜桃-www中文字幕-在线中文字幕av-亚洲欧美一区二区三区四区-撸久久-香蕉视频一区-久久无码精品丰满人妻-国产高潮av-激情福利社-日韩av网址大全-国产精品久久999-日本五十路在线-性欧美在线-久久99精品波多结衣一区-男女午夜免费视频-黑人极品ⅴideos精品欧美棵-人人妻人人澡人人爽精品欧美一区-日韩一区在线看-欧美a级在线免费观看

歡迎訪問 生活随笔!

生活随笔

當前位置: 首頁 > 编程资源 > 编程问答 >内容正文

编程问答

if快还是switch快?解密switch背后的秘密

發(fā)布時間:2025/3/11 编程问答 23 豆豆
生活随笔 收集整理的這篇文章主要介紹了 if快还是switch快?解密switch背后的秘密 小編覺得挺不錯的,現(xiàn)在分享給大家,幫大家做個參考.

這是我的第 57 篇原創(chuàng)文章

條件判斷語句是程序的重要組成部分,也是系統(tǒng)業(yè)務邏輯的控制手段。重要程度和使用頻率更是首屈一指,那我們要如何選擇 if 還是 switch 呢?他們的性能差別有多大?switch 性能背后的秘密是什么?接下來讓我們一起來尋找這些問題的答案。

switch VS if

我在之前的文章《9個小技巧讓你的 if else看起來更優(yōu)雅》中有提過,要盡量使用 switch 因為他的性能比較高,但具體高多少?以及為什么高的原因將在本文為你揭曉。

我們依然借助 Oracle 官方提供的 JMH(Java Microbenchmark Harness,JAVA 微基準測試套件)框架來進行測試,首先引入 JMH 框架,在 pom.xml 文件中添加如下配置:

<!--?https://mvnrepository.com/artifact/org.openjdk.jmh/jmh-core?--> <dependency><groupId>org.openjdk.jmh</groupId><artifactId>jmh-core</artifactId><version>1.23</version> </dependency>

然后編寫測試代碼,我們這里添加 5 個條件判斷分支,具體實現(xiàn)代碼如下:

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;@BenchmarkMode(Mode.AverageTime)?//?測試完成時間 @OutputTimeUnit(TimeUnit.NANOSECONDS) @Warmup(iterations?=?2,?time?=?1,?timeUnit?=?TimeUnit.SECONDS)?//?預熱?2?輪,每次?1s @Measurement(iterations?=?5,?time?=?1,?timeUnit?=?TimeUnit.SECONDS)?//?測試?5?輪,每次?3s @Fork(1)?//?fork?1?個線程 @State(Scope.Thread)?//?每個測試線程一個實例 public?class?SwitchOptimizeTest?{static?Integer?_NUM?=?9;public?static?void?main(String[]?args)?throws?RunnerException?{//?啟動基準測試Options?opt?=?new?OptionsBuilder().include(SwitchOptimizeTest.class.getSimpleName())?//?要導入的測試類.output("/Users/admin/Desktop/jmh-switch.log")?//?輸出測試結果的文件.build();new?Runner(opt).run();?//?執(zhí)行測試}@Benchmarkpublic?void?switchTest()?{int?num1;switch?(_NUM)?{case?1:num1?=?1;break;case?3:num1?=?3;break;case?5:num1?=?5;break;case?7:num1?=?7;break;case?9:num1?=?9;break;default:num1?=?-1;break;}}@Benchmarkpublic?void?ifTest()?{int?num1;if?(_NUM?==?1)?{num1?=?1;}?else?if?(_NUM?==?3)?{num1?=?3;}?else?if?(_NUM?==?5)?{num1?=?5;}?else?if?(_NUM?==?7)?{num1?=?7;}?else?if?(_NUM?==?9)?{num1?=?9;}?else?{num1?=?-1;}} }

以上代碼的測試結果如下:


備注:本文的測試環(huán)境為:JDK 1.8 / Mac mini (2018) / Idea 2020.1

從以上結果可以看出(Score 列),switch 的平均執(zhí)行完成時間比 if 的平均執(zhí)行完成時間快了約 2.33 倍

性能分析

為什么 switch 的性能會比 if 的性能高這么多?

這需要從他們字節(jié)碼說起,我們把他們的代碼使用 javac?生成字節(jié)碼如下所示:

public?class?com.example.optimize.SwitchOptimize?{static?java.lang.Integer?_NUM;public?com.example.optimize.SwitchOptimize();Code:0:?aload_01:?invokespecial?#1??????????????????//?Method?java/lang/Object."<init>":()V4:?returnpublic?static?void?main(java.lang.String[]);Code:0:?invokestatic??#7??????????????????//?Method?switchTest:()V3:?invokestatic??#12?????????????????//?Method?ifTest:()V6:?returnpublic?static?void?switchTest();Code:0:?getstatic?????#15?????????????????//?Field?_NUM:Ljava/lang/Integer;3:?invokevirtual?#19?????????????????//?Method?java/lang/Integer.intValue:()I6:?tableswitch???{?//?1?to?91:?562:?833:?614:?835:?666:?837:?718:?839:?77default:?83}56:?iconst_157:?istore_058:?goto??????????8561:?iconst_362:?istore_063:?goto??????????8566:?iconst_567:?istore_068:?goto??????????8571:?bipush????????773:?istore_074:?goto??????????8577:?bipush????????979:?istore_080:?goto??????????8583:?iconst_m184:?istore_085:?returnpublic?static?void?ifTest();Code:0:?getstatic?????#15?????????????????//?Field?_NUM:Ljava/lang/Integer;3:?invokevirtual?#19?????????????????//?Method?java/lang/Integer.intValue:()I6:?iconst_17:?if_icmpne?????1510:?iconst_111:?istore_012:?goto??????????8115:?getstatic?????#15?????????????????//?Field?_NUM:Ljava/lang/Integer;18:?invokevirtual?#19?????????????????//?Method?java/lang/Integer.intValue:()I21:?iconst_322:?if_icmpne?????3025:?iconst_326:?istore_027:?goto??????????8130:?getstatic?????#15?????????????????//?Field?_NUM:Ljava/lang/Integer;33:?invokevirtual?#19?????????????????//?Method?java/lang/Integer.intValue:()I36:?iconst_537:?if_icmpne?????4540:?iconst_541:?istore_042:?goto??????????8145:?getstatic?????#15?????????????????//?Field?_NUM:Ljava/lang/Integer;48:?invokevirtual?#19?????????????????//?Method?java/lang/Integer.intValue:()I51:?bipush????????753:?if_icmpne?????6256:?bipush????????758:?istore_059:?goto??????????8162:?getstatic?????#15?????????????????//?Field?_NUM:Ljava/lang/Integer;65:?invokevirtual?#19?????????????????//?Method?java/lang/Integer.intValue:()I68:?bipush????????970:?if_icmpne?????7973:?bipush????????975:?istore_076:?goto??????????8179:?iconst_m180:?istore_081:?returnstatic?{};Code:0:?iconst_11:?invokestatic??#25?????????????????//?Method?java/lang/Integer.valueOf:(I)Ljava/lang/Integer;4:?putstatic?????#15?????????????????//?Field?_NUM:Ljava/lang/Integer;7:?return }

這些字節(jié)碼中最重要的信息是“getstatic ? ? #15”,這段代碼表示取出“_NUM”變量和條件進行判斷。

從上面的字節(jié)碼可以看出,在 switch 中只取出了一次變量和條件進行比較,而 if 中每次都會取出變量和條件進行比較,因此 if 的效率就會比 switch 慢很多

提升測試量

前面的測試代碼我們使用了 5 個分支條件來測試了 if 和 switch 的性能,那如果把分支的判斷條件增加 3 倍(15 個)時,測試的結果又會怎么呢?

增加至 15 個分支判斷的實現(xiàn)代碼如下:

package?com.example.optimize;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;@BenchmarkMode(Mode.AverageTime)?//?測試完成時間 @OutputTimeUnit(TimeUnit.NANOSECONDS) @Warmup(iterations?=?2,?time?=?1,?timeUnit?=?TimeUnit.SECONDS)?//?預熱?2?輪,每次?1s @Measurement(iterations?=?5,?time?=?1,?timeUnit?=?TimeUnit.SECONDS)?//?測試?5?輪,每次?3s @Fork(1)?//?fork?1?個線程 @State(Scope.Thread)?//?每個測試線程一個實例 public?class?SwitchOptimizeTest?{static?Integer?_NUM?=?1;public?static?void?main(String[]?args)?throws?RunnerException?{//?啟動基準測試Options?opt?=?new?OptionsBuilder().include(SwitchOptimizeTest.class.getSimpleName())?//?要導入的測試類.output("/Users/admin/Desktop/jmh-switch.log")?//?輸出測試結果的文件.build();new?Runner(opt).run();?//?執(zhí)行測試}@Benchmarkpublic?void?switchTest()?{int?num1;switch?(_NUM)?{case?1:num1?=?1;break;case?2:num1?=?2;break;case?3:num1?=?3;break;case?4:num1?=?4;break;case?5:num1?=?5;break;case?6:num1?=?6;break;case?7:num1?=?7;break;case?8:num1?=?8;break;case?9:num1?=?9;break;case?10:num1?=?10;break;case?11:num1?=?11;break;case?12:num1?=?12;break;case?13:num1?=?13;break;case?14:num1?=?14;break;case?15:num1?=?15;break;default:num1?=?-1;break;}}@Benchmarkpublic?void?ifTest()?{int?num1;if?(_NUM?==?1)?{num1?=?1;}?else?if?(_NUM?==?2)?{num1?=?2;}?else?if?(_NUM?==?3)?{num1?=?3;}?else?if?(_NUM?==?4)?{num1?=?4;}?else?if?(_NUM?==?5)?{num1?=?5;}?else?if?(_NUM?==?6)?{num1?=?6;}?else?if?(_NUM?==?7)?{num1?=?7;}?else?if?(_NUM?==?8)?{num1?=?8;}?else?if?(_NUM?==?9)?{num1?=?9;}?else?if?(_NUM?==?10)?{num1?=?10;}?else?if?(_NUM?==?11)?{num1?=?11;}?else?if?(_NUM?==?12)?{num1?=?12;}?else?if?(_NUM?==?13)?{num1?=?13;}?else?if?(_NUM?==?14)?{num1?=?14;}?else?if?(_NUM?==?15)?{num1?=?15;}?else?{num1?=?-1;}} }

以上代碼的測試結果如下:


從 Score 的值可以看出,當分支判斷增加至 15 個,switch 的性能比 if 的性能高出了約 3.7 倍,而之前有 5 個分支判斷時的測試結果為,switch 的性能比 if 的性能高出了約 2.3 倍,也就是說分支的判斷條件越多,switch 性能高的特性體現(xiàn)的就越明顯

switch 的秘密

對于 switch 來說,他最終生成的字節(jié)碼有兩種形態(tài),一種是 tableswitch,另一種是 lookupswitch,決定最終生成的代碼使用那種形態(tài)取決于 switch 的判斷添加是否緊湊,例如到 case 是 1...2...3...4 這種依次遞增的判斷條件時,使用的是 tableswitch,而像 case 是 1...33...55...22 這種非緊湊型的判斷條件時則會使用 lookupswitch,測試代碼如下:

public?class?SwitchOptimize?{static?Integer?_NUM?=?1;public?static?void?main(String[]?args)?{tableSwitchTest();lookupSwitchTest();}public?static?void?tableSwitchTest()?{int?num1;switch?(_NUM)?{case?1:num1?=?1;break;case?2:num1?=?2;break;case?3:num1?=?3;break;case?4:num1?=?4;break;case?5:num1?=?5;break;case?6:num1?=?6;break;case?7:num1?=?7;break;case?8:num1?=?8;break;case?9:num1?=?9;break;default:num1?=?-1;break;}}public?static?void?lookupSwitchTest()?{int?num1;switch?(_NUM)?{case?1:num1?=?1;break;case?11:num1?=?2;break;case?3:num1?=?3;break;case?4:num1?=?4;break;case?19:num1?=?5;break;case?6:num1?=?6;break;case?33:num1?=?7;break;case?8:num1?=?8;break;case?999:num1?=?9;break;default:num1?=?-1;break;}} }

對應的字節(jié)碼如下:

public?class?com.example.optimize.SwitchOptimize?{static?java.lang.Integer?_NUM;public?com.example.optimize.SwitchOptimize();Code:0:?aload_01:?invokespecial?#1??????????????????//?Method?java/lang/Object."<init>":()V4:?returnpublic?static?void?main(java.lang.String[]);Code:0:?invokestatic??#7??????????????????//?Method?tableSwitchTest:()V3:?invokestatic??#12?????????????????//?Method?lookupSwitchTest:()V6:?returnpublic?static?void?tableSwitchTest();Code:0:?getstatic?????#15?????????????????//?Field?_NUM:Ljava/lang/Integer;3:?invokevirtual?#19?????????????????//?Method?java/lang/Integer.intValue:()I6:?tableswitch???{?//?1?to?91:?562:?613:?664:?715:?766:?817:?878:?939:?99default:?105}56:?iconst_157:?istore_058:?goto??????????10761:?iconst_262:?istore_063:?goto??????????10766:?iconst_367:?istore_068:?goto??????????10771:?iconst_472:?istore_073:?goto??????????10776:?iconst_577:?istore_078:?goto??????????10781:?bipush????????683:?istore_084:?goto??????????10787:?bipush????????789:?istore_090:?goto??????????10793:?bipush????????895:?istore_096:?goto??????????10799:?bipush????????9101:?istore_0102:?goto??????????107105:?iconst_m1106:?istore_0107:?returnpublic?static?void?lookupSwitchTest();Code:0:?getstatic?????#15?????????????????//?Field?_NUM:Ljava/lang/Integer;3:?invokevirtual?#19?????????????????//?Method?java/lang/Integer.intValue:()I6:?lookupswitch??{?//?91:?883:?984:?1036:?1138:?12511:?9319:?10833:?119999:?131default:?137}88:?iconst_189:?istore_090:?goto??????????13993:?iconst_294:?istore_095:?goto??????????13998:?iconst_399:?istore_0100:?goto??????????139103:?iconst_4104:?istore_0105:?goto??????????139108:?iconst_5109:?istore_0110:?goto??????????139113:?bipush????????6115:?istore_0116:?goto??????????139119:?bipush????????7121:?istore_0122:?goto??????????139125:?bipush????????8127:?istore_0128:?goto??????????139131:?bipush????????9133:?istore_0134:?goto??????????139137:?iconst_m1138:?istore_0139:?returnstatic?{};Code:0:?iconst_11:?invokestatic??#25?????????????????//?Method?java/lang/Integer.valueOf:(I)Ljava/lang/Integer;4:?putstatic?????#15?????????????????//?Field?_NUM:Ljava/lang/Integer;7:?return }

從上面字節(jié)碼可以看出 tableSwitchTest 使用的 tableswitch,而 lookupSwitchTest 則是使用的 lookupswitch。

tableswitch VS?lookupSwitchTest

當執(zhí)行一次 tableswitch 時,堆棧頂部的 int 值直接用作表中的索引,以便抓取跳轉目標并立即執(zhí)行跳轉。也就是說 tableswitch 的存儲結構類似于數組,是直接用索引獲取元素的,所以整個查詢的時間復雜度是 O(1),這也意味著它的搜索速度非常快。

而執(zhí)行 lookupswitch 時,會逐個進行分支比較或者使用二分法進行查詢,因此查詢時間復雜度是 O(log n),所以使用 lookupswitch 會比 tableswitch 慢

接下來我們使用實際的代碼測試一下,他們兩個之間的性能,測試代碼如下:

package?com.example.optimize;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;@BenchmarkMode(Mode.AverageTime)?//?測試完成時間 @OutputTimeUnit(TimeUnit.NANOSECONDS) @Warmup(iterations?=?2,?time?=?1,?timeUnit?=?TimeUnit.SECONDS)?//?預熱?2?輪,每次?1s @Measurement(iterations?=?5,?time?=?1,?timeUnit?=?TimeUnit.SECONDS)?//?測試?5?輪,每次?3s @Fork(1)?//?fork?1?個線程 @State(Scope.Thread)?//?每個測試線程一個實例 public?class?SwitchOptimizeTest?{static?Integer?_NUM?=?-1;public?static?void?main(String[]?args)?throws?RunnerException?{//?啟動基準測試Options?opt?=?new?OptionsBuilder().include(SwitchOptimizeTest.class.getSimpleName())?//?要導入的測試類.build();new?Runner(opt).run();?//?執(zhí)行測試}@Benchmarkpublic?void?tableSwitchTest()?{int?num1;switch?(_NUM)?{case?1:num1?=?1;break;case?2:num1?=?2;break;case?3:num1?=?3;break;case?4:num1?=?4;break;case?5:num1?=?5;break;case?6:num1?=?6;break;case?7:num1?=?7;break;case?8:num1?=?8;break;case?9:num1?=?9;break;default:num1?=?-1;break;}}@Benchmarkpublic?void?lookupSwitchTest()?{int?num1;switch?(_NUM)?{case?1:num1?=?1;break;case?11:num1?=?2;break;case?3:num1?=?3;break;case?4:num1?=?4;break;case?19:num1?=?5;break;case?6:num1?=?6;break;case?33:num1?=?7;break;case?8:num1?=?8;break;case?999:num1?=?9;break;default:num1?=?-1;break;}} }

以上代碼的測試結果如下:


可以看出在分支判斷為 9 個時,tableswitch 的性能比 lookupwitch 的性能快了約 1.3 倍。但即使這樣 lookupwitch 依然比 if 查詢性能要高很多

總結

switch 的判斷條件是 5 個時,性能比 if 高出了約 2.3 倍,而當判斷條件的數量越多時,他們的性能相差就越大。而 switch 在編譯為字節(jié)碼時,會根據 switch 的判斷條件是否緊湊生成兩種代碼:tableswitch(緊湊時生成)和 lookupswitch(非緊湊時生成),其中 tableswitch 是采用類似于數組的存儲結構,直接根據索引查詢元素;而 lookupswitch 則需要逐個查詢或者使用二分法查詢,因此?tableswitch 的性能會比?lookupswitch 的性能高,但無論如何 switch 的性能都比 if 的性能要高

最后的話

原創(chuàng)不易,如果覺得本文對你有用,請隨手點擊一個「」,這是對作者最大的支持與鼓勵,謝謝你。

參考 & 鳴謝

https://www.javaguides.net/2020/03/5-best-ways-to-iterate-over-hashmap-in-java.html

HashMap 的 7 種遍歷方式與性能分析!「修正篇」

String性能提升10倍的幾個方法!(源碼+原理分析)

關注公眾號「Java中文社群」回復“干貨”,獲取原創(chuàng)干貨 Top 榜

關注公眾號發(fā)送”進群“,老王拉你進讀者群。

總結

以上是生活随笔為你收集整理的if快还是switch快?解密switch背后的秘密的全部內容,希望文章能夠幫你解決所遇到的問題。

如果覺得生活随笔網站內容還不錯,歡迎將生活随笔推薦給好友。