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

歡迎訪問 生活随笔!

生活随笔

當前位置: 首頁 > 编程语言 > java >内容正文

java

Java 中的5个代码性能提升技巧,最高提升近10倍

發布時間:2025/3/15 java 36 豆豆
生活随笔 收集整理的這篇文章主要介紹了 Java 中的5个代码性能提升技巧,最高提升近10倍 小編覺得挺不錯的,現在分享給大家,幫大家做個參考.

這篇文章介紹幾個 Java 開發中可以進行性能優化的小技巧,雖然大多數情況下極致優化代碼是沒有必要的,但是作為一名技術開發者,我們還是想追求代碼的更小、更快,更強。如果哪天你發現程序的運行速度不盡人意,可能會想到這篇文章。

提示:我們不應該為了優化而優化,這有時會增加代碼的復雜度。

這篇文章中的代碼都在以下環境中進行性能測試。

  • JMH version: 1.33(Java 基準測試框架)

  • VM version: JDK 17, OpenJDK 64-Bit Server VM, 17+35-2724

通過這篇文章的測試,將發現以下幾個操作的性能差異。

  • 預先分配 HashMap 的大小,提高 1/4 的性能。

  • 優化 HashMap 的 key,性能相差 9.5 倍。

  • 不使用 ?Enum.values() 遍歷,Spring 也曾如此優化。

  • 使用 Enum 代替 String 常量,性能高出 1.5 倍。

  • 使用高版本 JDK,基礎操作有 2-5 倍性能差異。

  • ?

    預先分配 HashMap 的大小

    HashMap 是 Java 中最為常用的集合之一,大多數的操作速度都非常快,但是 HashMap 在調整自身的容量大小時是很慢且難以自動優化,因此我們在定義一個 HashMap 之前,應該盡可能的給出它的容量大小。給出 size 值時要考慮負載因子,HashMap 默認負載因子是 0.75,也就是要設置的 size 值要除于 0.75。

    相關文章:HashMap 源碼分析解讀

    下面使用 JMH 進行基準測試,測試分別向初始容量為 16 和 32 的 HashMap 中插入 14 個元素的效率。

    /***?@author?https://www.wdbyte.com*/ @State(Scope.Benchmark) @Warmup(iterations?=?3,time?=?3) @Measurement(iterations?=?5,time?=?3) public?class?HashMapSize?{@Param({"14"})int?keys;@Param({"16",?"32"})int?size;@Benchmarkpublic?HashMap<Integer,?Integer>?getHashMap()?{HashMap<Integer,?Integer>?map?=?new?HashMap<>(size);for?(int?i?=?0;?i?<?keys;?i++)?{map.put(i,?i);}return?map;} }

    HashMap 的初始容量是 16,負責因子 0.75,即最多插入 12 個元素,再插入時就要進行擴容,所以插入 14 個元素過程中需要擴容一次,但是如果 HashMap 初始化時就給了 32 容量,那么最多可以承載 32 * 0.75 = 24 個元素,所以插入 14 個元素時是不需要擴容操作的。

    #?JMH?version:?1.33 #?VM?version:?JDK?17,?OpenJDK?64-Bit?Server?VM,?17+35-2724Benchmark???????????????(keys)??(size)???Mode??Cnt????????Score????????Error??Units HashMapSize.getHashMap??????14??????16??thrpt???25??4825825.152?±?323910.557??ops/s HashMapSize.getHashMap??????14??????32??thrpt???25??6556184.664?±?711657.679??ops/s

    可以看到在這次測試中,初始容量為32 的 HashMap 比初始容量為 16 的 HashMap 每秒可以多操作 26% 次,已經有 1/4 的性能差異了。

    ?

    優化 HashMap 的 key

    如果 HashMap 的 key 值需要用到多個 String 字符串時,把字符串作為某個類屬性,然后使用這個類的實例作為 key 會比使用字符串拼接效率更高。

    下面測試使用兩個字符串拼接作為 key,和把兩個字符串作為 MutablePair 類的屬性引用,然后使用 MutablePair 對象作為 key 的運行效率差異。

    /***?@author?https://www.wdbyte.com*/ @State(Scope.Benchmark) @Warmup(iterations?=?3,?time?=?3) @Measurement(iterations?=?5,?time?=?3) public?class?HashMapKey?{private?int?size?=?1024;private?Map<String,?Object>?stringMap;private?Map<Pair,?Object>?pairMap;private?String[]?prefixes;private?String[]?suffixes;@Setup(Level.Trial)public?void?setup()?{prefixes?=?new?String[size];suffixes?=?new?String[size];stringMap?=?new?HashMap<>();pairMap?=?new?HashMap<>();for?(int?i?=?0;?i?<?size;?++i)?{prefixes[i]?=?UUID.randomUUID().toString();suffixes[i]?=?UUID.randomUUID().toString();stringMap.put(prefixes[i]?+?";"?+?suffixes[i],?i);//?use?new?String?to?avoid?reference?equality?speeding?up?the?equals?callspairMap.put(new?MutablePair(prefixes[i],?suffixes[i]),?i);}}@Benchmark@OperationsPerInvocation(1024)public?void?stringKey(Blackhole?bh)?{for?(int?i?=?0;?i?<?prefixes.length;?i++)?{bh.consume(stringMap.get(prefixes[i]?+?";"?+?suffixes[i]));}}@Benchmark@OperationsPerInvocation(1024)public?void?pairMap(Blackhole?bh)?{for?(int?i?=?0;?i?<?prefixes.length;?i++)?{bh.consume(pairMap.get(new?MutablePair(prefixes[i],?suffixes[i])));}} }

    測試結果:

    #?JMH?version:?1.33 #?VM?version:?JDK?17,?OpenJDK?64-Bit?Server?VM,?17+35-2724Benchmark??????????????Mode??Cnt?????????Score?????????Error??Units HashMapKey.pairMap????thrpt???25??89295035.436?±?6498403.173??ops/s HashMapKey.stringKey??thrpt???25???9410641.728?±??389850.653??ops/s

    可以發現使用對象引用作為 key 的性能,是使用 String 拼接作為 key 的性能的 9.5 倍

    ?

    不使用 ?Enum.values() 遍歷

    我們通常會使用 ?Enum.values() 進行枚舉類遍歷,但是這樣每次調用都會分配枚舉類值數量大小的數組用于操作,這里完全可以緩存起來,以減少每次內存分配的時間和空間消耗。

    /***?枚舉類遍歷測試**?@author?https://www.wdbyte.com*/ @State(Scope.Benchmark) @Warmup(iterations?=?3,?time?=?3) @Measurement(iterations?=?5,?time?=?3) @BenchmarkMode(Mode.AverageTime) @OutputTimeUnit(TimeUnit.MILLISECONDS) public?class?EnumIteration?{enum?FourteenEnum?{a,b,c,d,e,f,g,h,i,j,k,l,m,n;static?final?FourteenEnum[]?VALUES;static?{VALUES?=?values();}}@Benchmarkpublic?void?valuesEnum(Blackhole?bh)?{for?(FourteenEnum?value?:?FourteenEnum.values())?{bh.consume(value.ordinal());}}@Benchmarkpublic?void?enumSetEnum(Blackhole?bh)?{for?(FourteenEnum?value?:?EnumSet.allOf(FourteenEnum.class))?{bh.consume(value.ordinal());}}@Benchmarkpublic?void?cacheEnums(Blackhole?bh)?{for?(FourteenEnum?value?:?FourteenEnum.VALUES)?{bh.consume(value.ordinal());}} }

    運行結果

    #?JMH?version:?1.33 #?VM?version:?JDK?17,?OpenJDK?64-Bit?Server?VM,?17+35-2724Benchmark???????????????????Mode??Cnt?????????Score?????????Error??Units EnumIteration.cacheEnums???thrpt???25??15623401.567?±?2274962.772??ops/s EnumIteration.enumSetEnum??thrpt???25???8597188.662?±??610632.249??ops/s EnumIteration.valuesEnum???thrpt???25??14713941.570?±??728955.826??ops/s

    很明顯使用緩存后的遍歷速度是最快的,使用 EnumSet 遍歷效率是最低的,這很好理解,數組的遍歷效率是大于哈希表的。

    可能你會覺得這里使用 values() 緩存和直接使用 Enum.values() 的效率差異很小,其實在某些調用頻率很高的場景下是有很大區別的,在 Spring 框架中,曾使用 Enum.values() 這種方式在每次響應時遍歷 HTTP 狀態碼枚舉類,這在請求量大時造成了不必要的性能開銷,后來進行了 values() 緩存優化。

    下面是這次提交的截圖:

    Spring Enum.values 改動

    ?

    使用 Enum 代替 String 常量

    使用 Enum 枚舉類代替 String 常量有明顯的好處,枚舉類強制驗證,不會出錯,同時使用枚舉類的效率也更高。即使作為 Map 的 key 值來看,雖然 HashMap 的速度已經很快了,但是使用 EnumMap 的速度可以更快。

    提示:不要為了優化而優化,這會增加代碼的復雜度。

    下面測試使用使用 Enum 作為 key,和使用 String 作為 key,在 map.get 操作下的性能差異。

    /***?@author?https://www.wdbyte.com*/ @State(Scope.Benchmark) @Warmup(iterations?=?3,?time?=?3) @Measurement(iterations?=?5,?time?=?3) public?class?EnumMapBenchmark?{enum?AnEnum?{a,?b,?c,?d,?e,?f,?g,h,?i,?j,?k,?l,?m,?n,o,?p,?q,????r,?s,?t,u,?v,?w,????x,?y,?z;}/**?要查找的?key?的數量?*/private?static?int?size?=?10000;/**?隨機數種子?*/private?static?int?seed?=?99;@State(Scope.Benchmark)public?static?class?EnumMapState?{private?EnumMap<AnEnum,?String>?map;private?AnEnum[]?values;@Setup(Level.Trial)public?void?setup()?{map?=?new?EnumMap<>(AnEnum.class);values?=?new?AnEnum[size];AnEnum[]?enumValues?=?AnEnum.values();SplittableRandom?random?=?new?SplittableRandom(seed);for?(int?i?=?0;?i?<?size;?i++)?{int?nextInt?=?random.nextInt(0,?Integer.MAX_VALUE);values[i]?=?enumValues[nextInt?%?enumValues.length];}for?(AnEnum?value?:?enumValues)?{map.put(value,?UUID.randomUUID().toString());}}}@State(Scope.Benchmark)public?static?class?HashMapState{private?HashMap<String,?String>?map;private?String[]?values;@Setup(Level.Trial)public?void?setup()?{map?=?new?HashMap<>();values?=?new?String[size];AnEnum[]?enumValues?=?AnEnum.values();int?pos?=?0;SplittableRandom?random?=?new?SplittableRandom(seed);for?(int?i?=?0;?i?<?size;?i++)?{int?nextInt?=?random.nextInt(0,?Integer.MAX_VALUE);values[i]?=?enumValues[nextInt?%?enumValues.length].toString();}for?(AnEnum?value?:?enumValues)?{map.put(value.toString(),?UUID.randomUUID().toString());}}}@Benchmarkpublic?void?enumMap(EnumMapState?state,?Blackhole?bh)?{for?(AnEnum?value?:?state.values)?{bh.consume(state.map.get(value));}}@Benchmarkpublic?void?hashMap(HashMapState?state,?Blackhole?bh)?{for?(String?value?:?state.values)?{bh.consume(state.map.get(value));}} }

    運行結果:

    #?JMH?version:?1.33 #?VM?version:?JDK?17,?OpenJDK?64-Bit?Server?VM,?17+35-2724Benchmark??????????????????Mode??Cnt??????Score??????Error??Units EnumMapBenchmark.enumMap??thrpt???25??22159.232?±?1268.800??ops/s EnumMapBenchmark.hashMap??thrpt???25??14528.555?±?1323.610??ops/s

    很明顯,使用 Enum 作為 key 的性能比使用 String 作為 key 的性能高出 1.5 倍。但是仍然要根據實際情況考慮是否使用 EnumMap 和 EnumSet。

    ?

    使用高版本 JDK

    String 類應該是 Java 中使用頻率最高的類了,但是 Java 8 中的 ?String 實現相比高版本 JDK ,則占用空間更多,性能更低。

    下面測試 String 轉 bytes 和 bytes 轉 String 在 Java 8 以及 Java 11 中的性能開銷。

    /***?@author?https://www.wdbyte.com*?@date?2021/12/23*/ @State(Scope.Benchmark) @Warmup(iterations?=?3,?time?=?3) @Measurement(iterations?=?5,?time?=?3) public?class?StringInJdk?{@Param({"10000"})private?int?size;private?String[]?stringArray;private?List<byte[]>?byteList;@Setup(Level.Trial)public?void?setup()?{byteList?=?new?ArrayList<>(size);stringArray?=?new?String[size];for?(int?i?=?0;?i?<?size;?i++)?{String?uuid?=?UUID.randomUUID().toString();stringArray[i]?=?uuid;byteList.add(uuid.getBytes(StandardCharsets.UTF_8));}}@Benchmarkpublic?void?byteToString(Blackhole?bh)?{for?(byte[]?bytes?:?byteList)?{bh.consume(new?String(bytes,?StandardCharsets.UTF_8));}}@Benchmarkpublic?void?stringToByte(Blackhole?bh)?{for?(String?s?:?stringArray)?{bh.consume(s.getBytes(StandardCharsets.UTF_8));}} }

    測試結果:

    #?JMH?version:?1.33 #?VM?version:?JDK?1.8.0_151,?Java?HotSpot(TM)?64-Bit?Server?VM,?25.151-b12Benchmark?????????????????(size)???Mode??Cnt?????Score?????Error??Units StringInJdk.byteToString???10000??thrpt???25??2396.713?±?133.500??ops/s StringInJdk.stringToByte???10000??thrpt???25??1745.060?±??16.945??ops/s#?JMH?version:?1.33 #?VM?version:?JDK?17,?OpenJDK?64-Bit?Server?VM,?17+35-2724Benchmark?????????????????(size)???Mode??Cnt?????Score?????Error??Units StringInJdk.byteToString???10000??thrpt???25??5711.954?±??41.865??ops/s StringInJdk.stringToByte???10000??thrpt???25??8595.895?±?704.004??ops/s

    可以看到在 bytes 轉 String 操作上,Java 17 的性能是 Java 8 的 2.5 倍左右,而 String 轉 bytes 操作,Java 17 的性能是 Java 8 的 5 倍。關于字符串的操作非常基礎,隨處可見,可見高版本的優勢十分明顯。

    參考

    https://richardstartin.github.io/posts/5-java-mundane-performance-tricks

    https://github.com/spring-projects/spring-framework/issues/26842

    有道無術,術可成;有術無道,止于術

    歡迎大家關注Java之道公眾號

    好文章,我在看??

    總結

    以上是生活随笔為你收集整理的Java 中的5个代码性能提升技巧,最高提升近10倍的全部內容,希望文章能夠幫你解決所遇到的問題。

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