Java代碼中4種字符串拼接方式分析
本文研討的字符串拼接方式為以下4種:“+”號、StringBuilder、StringJoiner、String#join,對比分析及探討最佳實踐。
結(jié)論
后面內(nèi)容比較枯燥,所以先說結(jié)論:
- 本文研討的字符串拼接方式為以下4種:“+”號、StringBuilder、StringJoiner、String#join
- 在簡單的字符串拼接場景中「如:"a" + "b" + "c"」,以上四種方式性能無明顯差異。
- 在循環(huán)字符串拼接的場景下,使用“+”號性能最低,其他三種方式性能也無明顯差異,但是根據(jù)驗證結(jié)果可粗淺發(fā)現(xiàn),指定初始容量的StringBuilder效率最高。當(dāng)然不光考慮性能,也要考慮垃圾回收效率的問題,避免OOM。
- 本文最后補充對比了StringBuffer,在無爭搶共享資源的場景下,StringBuffer性能并未明顯變差。
最佳實踐
- 阿里巴巴Java開發(fā)手冊-日志規(guī)約「5」可進(jìn)行優(yōu)化:使用占位符的形式可讀性、便捷性不佳,可考慮使用Lambda,延遲字符串的拼接,且使用更加便利。
- 阿里巴巴Java開發(fā)手冊-OOP 規(guī)約「23」可進(jìn)行優(yōu)化:循環(huán)拼接時須使用StringBuilder;在拼接大量的大容量字符串時,使用StringBuilder盡量指定初始容量。
- 簡單的字符串拼接可用任意方式,推薦直接使用“+”號拼接,可讀性最優(yōu)。
- 盡量使用JDK等直接提供的特性「如“+”號拼接字符串,Synchronized關(guān)鍵詞等」,因為編譯器+JVM會持續(xù)對此進(jìn)行優(yōu)化,JDK升級即可獲得更大的收益。除非有明確的理由可以自行實現(xiàn)類似的功能。
- 在需要考慮線程安全的場景可以考慮使用StringBuffer進(jìn)行字符串拼接,不過一般來說沒有這種需求,故不應(yīng)該使用StringBuffer,避免增加復(fù)雜性。
分析過程
環(huán)境
- 系統(tǒng): windows 10 21H1
- JDK: OpenJDK 1.8.0_302
分析用示例代碼:
@Slf4j public class StringConcat { ? ? @SneakyThrows ? ? public static void main(String[] args) { ? ? ? ? log.info("java虛擬機預(yù)熱開始"); ? ? ? ? String[] strs = new String[6000000]; ? ? ? ? for (int i = 0; i < strs.length; i++) { ? ? ? ? ? ? strs[i] = id(); ? ? ? ? } ? ? ? ? loopStringJoiner(strs); ? ? ? ? loopStringJoin(strs); ? ? ? ? loopStringBuilder(strs); ? ? ? ? log.info("java虛擬機預(yù)熱結(jié)束"); ? ? ? ? Thread.sleep(1000); ? ? ? ? log.info("開始測試:"); ? ? ? ? Thread.sleep(1000); ? ? ? ? Stopwatch stopwatchLoopPlus = Stopwatch.createStarted(); // ? ? ? ?loopPlus(strs); ? ? ? ? log.info("loop-plus: " + stopwatchLoopPlus.elapsed(TimeUnit.MILLISECONDS)); ? ? ? ? Thread.sleep(1000); ? ? ? ? Stopwatch stopwatchLoopStringBuilderCapacity = Stopwatch.createStarted(); ? ? ? ? loopStringBuilderCapacity(strs); ? ? ? ? log.info("loop-stringBuilderCapacity: " + stopwatchLoopStringBuilderCapacity.elapsed(TimeUnit.MILLISECONDS)); ? ? ? ? Thread.sleep(1000); ? ? ? ? Stopwatch stopwatchLoopStringBuilder = Stopwatch.createStarted(); ? ? ? ? loopStringBuilder(strs); ? ? ? ? log.info("loop-stringBuilder: " + stopwatchLoopStringBuilder.elapsed(TimeUnit.MILLISECONDS)); ? ? ? ? Thread.sleep(1000); ? ? ? ? Stopwatch stopwatchLoopJoin = Stopwatch.createStarted(); ? ? ? ? loopStringJoin(strs); ? ? ? ? log.info("loop-String.join: " + stopwatchLoopJoin.elapsed(TimeUnit.MILLISECONDS)); ? ? ? ? Thread.sleep(1000); ? ? ? ? Stopwatch stopwatchLoopStringJoiner = Stopwatch.createStarted(); ? ? ? ? loopStringJoiner(strs); ? ? ? ? log.info("loop-stringJoiner: " + stopwatchLoopStringJoiner.elapsed(TimeUnit.MILLISECONDS)); ? ? ? ? Thread.sleep(1000); ? ? ? ? Stopwatch stopwatchSimplePlus = Stopwatch.createStarted(); ? ? ? ? for (int i = 0; i < 500000; i++) { ? ? ? ? ? ? simplePlus(id(), id(), id()); ? ? ? ? } ? ? ? ? log.info("simple-Plus: " + stopwatchSimplePlus.elapsed(TimeUnit.MILLISECONDS)); ? ? ? ? Thread.sleep(1000); ? ? ? ? Stopwatch stopwatchSimpleStringBuilder = Stopwatch.createStarted(); ? ? ? ? for (int i = 0; i < 500000; i++) { ? ? ? ? ? ? simpleStringBuilder(id(), id(), id()); ? ? ? ? } ? ? ? ? log.info("simple-StringBuilder: " + stopwatchSimpleStringBuilder.elapsed(TimeUnit.MILLISECONDS)); ? ? ? ? Thread.sleep(1000); ? ? ? ? Stopwatch stopwatchSimpleStringBuffer = Stopwatch.createStarted(); ? ? ? ? for (int i = 0; i < 500000; i++) { ? ? ? ? ? ? simpleStringBuffer(id(), id(), id()); ? ? ? ? } ? ? ? ? log.info("simple-StringBuffer: " + stopwatchSimpleStringBuffer.elapsed(TimeUnit.MILLISECONDS)); ? ? } ? ? private static String loopPlus(String[] strs) { ? ? ? ? String str = ""; ? ? ? ? for (String s : strs) { ? ? ? ? ? ? str = str + "+" + s; ? ? ? ? } ? ? ? ? return str; ? ? } ? ? private static String loopStringBuilder(String[] strs) { ? ? ? ? StringBuilder str = new StringBuilder(); ? ? ? ? for (String s : strs) { ? ? ? ? ? ? str.append("+"); ? ? ? ? ? ? str.append(s); ? ? ? ? } ? ? ? ? return str.toString(); ? ? } ? ? private static String loopStringBuilderCapacity(String[] strs) { ? ? ? ? StringBuilder str = new StringBuilder(strs[0].length() * strs.length); ? ? ? ? for (String s : strs) { ? ? ? ? ? ? str.append("+"); ? ? ? ? ? ? str.append(s); ? ? ? ? } ? ? ? ? return str.toString(); ? ? } ? ? private static String loopStringJoin(String[] strs) { ? ? ? ? StringJoiner joiner = new StringJoiner("+"); ? ? ? ? for (String str : strs) { ? ? ? ? ? ? joiner.add(str); ? ? ? ? } ? ? ? ? return joiner.toString(); ? ? } ? ? private static String loopStringJoiner(String[] strs) { ? ? ? ? return String.join("+", strs); ? ? } ? ? private static String simplePlus(String a, String b, String c) { ? ? ? ? return a + "+" + b + "+" + c; ? ? } ? ? private static String simpleStringBuilder(String a, String b, String c) { ? ? ? ? StringBuilder builder = new StringBuilder(); ? ? ? ? builder.append(a); ? ? ? ? builder.append("+"); ? ? ? ? builder.append(b); ? ? ? ? builder.append("+"); ? ? ? ? builder.append(c); ? ? ? ? return builder.toString(); ? ? } ? ? private static String simpleStringBuffer(String a, String b, String c) { ? ? ? ? StringBuffer buffer = new StringBuffer(); ? ? ? ? buffer.append(a); ? ? ? ? buffer.append("+"); ? ? ? ? buffer.append(b); ? ? ? ? buffer.append("+"); ? ? ? ? buffer.append(c); ? ? ? ? return buffer.toString(); ? ? } ? ? private static String id() { ? ? ? ? return UUID.randomUUID().toString(); ? ? } }
結(jié)果及總結(jié)
- java虛擬機預(yù)熱開始
- java虛擬機預(yù)熱結(jié)束
- 開始測試:
- loop-plus: 執(zhí)行超時
- loop-stringBuilderCapacity: 285
- loop-stringBuilder: 1968
- loop-String.join: 1313
- loop-stringJoiner: 1238
- simple-Plus: 812
- simple-StringBuilder: 840
- simple-StringBuffer: 857
- 多次測試,可發(fā)現(xiàn)在字符串循環(huán)拼接場景下,直接使用“+”號性能最低,有初始容量的StringBuilder性能最高,其他方式性能均沒有太大差異。
- 多次測試,可發(fā)現(xiàn)在字符串簡單拼接場景下,使用“+”號、StringBuilder、StringBuffer性能差距在5%左右,可理解為測試誤差,可認(rèn)為三種方式性能一致。
代碼及結(jié)果分析
1. StringBuilder與StringBuffer對比
在無爭搶共享資源的場景下,JVM會使用偏向鎖等方法優(yōu)化,甚至?xí)M(jìn)行鎖消除,使用Synchronized關(guān)鍵詞與否,性能并無明顯差異。
2. 字節(jié)碼分析
對比上述#simplePlus和#simpleStringBuilder兩個方法的字節(jié)碼,可明顯看到兩方法執(zhí)行內(nèi)容基本一致,但是直接使用"+"號時處理流程更短,可見編譯器進(jìn)行了深度優(yōu)化,使用優(yōu)化后的字節(jié)碼理論上會有更高的性能:
? // access flags 0xA ? private static simplePlus(Ljava/lang/String;Ljava/lang/String;Ljava/lang/String;)Ljava/lang/String; ? ? // parameter ?a ? ? // parameter ?b ? ? // parameter ?c ? ?L0 ? ? LINENUMBER 125 L0 ? ? NEW java/lang/StringBuilder ? ? DUP ? ? INVOKESPECIAL java/lang/StringBuilder.<init> ()V ? ? ALOAD 0 ? ? INVOKEVIRTUAL java/lang/StringBuilder.append (Ljava/lang/String;)Ljava/lang/StringBuilder; ? ? LDC "+" ? ? INVOKEVIRTUAL java/lang/StringBuilder.append (Ljava/lang/String;)Ljava/lang/StringBuilder; ? ? ALOAD 1 ? ? INVOKEVIRTUAL java/lang/StringBuilder.append (Ljava/lang/String;)Ljava/lang/StringBuilder; ? ? LDC "+" ? ? INVOKEVIRTUAL java/lang/StringBuilder.append (Ljava/lang/String;)Ljava/lang/StringBuilder; ? ? ALOAD 2 ? ? INVOKEVIRTUAL java/lang/StringBuilder.append (Ljava/lang/String;)Ljava/lang/StringBuilder; ? ? INVOKEVIRTUAL java/lang/StringBuilder.toString ()Ljava/lang/String; ? ? ARETURN ? ?L1 ? ? LOCALVARIABLE a Ljava/lang/String; L0 L1 0 ? ? LOCALVARIABLE b Ljava/lang/String; L0 L1 1 ? ? LOCALVARIABLE c Ljava/lang/String; L0 L1 2 ? ? MAXSTACK = 2 ? ? MAXLOCALS = 3 ? // access flags 0xA ? private static simpleStringBuilder(Ljava/lang/String;Ljava/lang/String;Ljava/lang/String;)Ljava/lang/String; ? ? // parameter ?a ? ? // parameter ?b ? ? // parameter ?c ? ?L0 ? ? LINENUMBER 129 L0 ? ? NEW java/lang/StringBuilder ? ? DUP ? ? INVOKESPECIAL java/lang/StringBuilder.<init> ()V ? ? ASTORE 3 ? ?L1 ? ? LINENUMBER 130 L1 ? ? ALOAD 3 ? ? ALOAD 0 ? ? INVOKEVIRTUAL java/lang/StringBuilder.append (Ljava/lang/String;)Ljava/lang/StringBuilder; ? ? POP ? ?L2 ? ? LINENUMBER 131 L2 ? ? ALOAD 3 ? ? LDC "+" ? ? INVOKEVIRTUAL java/lang/StringBuilder.append (Ljava/lang/String;)Ljava/lang/StringBuilder; ? ? POP ? ?L3 ? ? LINENUMBER 132 L3 ? ? ALOAD 3 ? ? ALOAD 1 ? ? INVOKEVIRTUAL java/lang/StringBuilder.append (Ljava/lang/String;)Ljava/lang/StringBuilder; ? ? POP ? ?L4 ? ? LINENUMBER 133 L4 ? ? ALOAD 3 ? ? LDC "+" ? ? INVOKEVIRTUAL java/lang/StringBuilder.append (Ljava/lang/String;)Ljava/lang/StringBuilder; ? ? POP ? ?L5 ? ? LINENUMBER 134 L5 ? ? ALOAD 3 ? ? ALOAD 2 ? ? INVOKEVIRTUAL java/lang/StringBuilder.append (Ljava/lang/String;)Ljava/lang/StringBuilder; ? ? POP ? ?L6 ? ? LINENUMBER 135 L6 ? ? ALOAD 3 ? ? INVOKEVIRTUAL java/lang/StringBuilder.toString ()Ljava/lang/String; ? ? ARETURN ? ?L7 ? ? LOCALVARIABLE a Ljava/lang/String; L0 L7 0 ? ? LOCALVARIABLE b Ljava/lang/String; L0 L7 1 ? ? LOCALVARIABLE c Ljava/lang/String; L0 L7 2 ? ? LOCALVARIABLE builder Ljava/lang/StringBuilder; L1 L7 3 ? ? MAXSTACK = 2 ? ? MAXLOCALS = 4
到此這篇關(guān)于Java代碼中4種字符串拼接方式分析的文章就介紹到這了,更多相關(guān)Java 字符串拼接內(nèi)容請搜索腳本之家以前的文章或繼續(xù)瀏覽下面的相關(guān)文章希望大家以后多多支持腳本之家!
相關(guān)文章
使用quartz時,傳入?yún)?shù)到j(luò)ob中的使用記錄
這篇文章主要介紹了使用quartz時,傳入?yún)?shù)到j(luò)ob中的使用記錄,具有很好的參考價值,希望對大家有所幫助,如有錯誤或未考慮完全的地方,望不吝賜教2023-12-12IntelliJ IDEA搜索整個項目進(jìn)行全局替換(有危險慎用)
今天小編就為大家分享一篇關(guān)于IntelliJ IDEA搜索整個項目進(jìn)行全局替換(有危險慎用),小編覺得內(nèi)容挺不錯的,現(xiàn)在分享給大家,具有很好的參考價值,需要的朋友一起跟隨小編來看看吧2018-10-10spring boot 自動更新靜態(tài)文件和后臺代碼的實例
下面小編就為大家分享一篇spring boot 自動更新靜態(tài)文件和后臺代碼的實例,具有很好的參考價值,希望對大家有所幫助。一起跟隨小編過來看看吧2017-12-12詳解Spring的兩種代理方式:JDK動態(tài)代理和CGLIB動態(tài)代理
這篇文章主要介紹了詳解Spring的兩種代理方式:JDK動態(tài)代理和CGLIB動態(tài)代理,小編覺得挺不錯的,現(xiàn)在分享給大家,也給大家做個參考。一起跟隨小編過來看看吧2018-04-04spring schedule實現(xiàn)動態(tài)配置執(zhí)行時間
這篇文章主要介紹了spring schedule實現(xiàn)動態(tài)配置執(zhí)行時間,具有很好的參考價值,希望對大家有所幫助。如有錯誤或未考慮完全的地方,望不吝賜教2021-11-11深入了解Maven Settings.xml文件的結(jié)構(gòu)和功能
這篇文章主要為大家介紹了Maven Settings.xml文件基本結(jié)構(gòu)和功能詳解,有需要的朋友可以借鑒參考下,希望能夠有所幫助,祝大家多多進(jìn)步,早日升職加薪2023-11-11詳解Java使用Pipeline對Redis批量讀寫(hmset&hgetall)
本篇文章主要介紹了Java使用Pipeline對Redis批量讀寫(hmset&hgetall),具有一定的參考價值,有興趣的可以了解一下。2016-12-12Java中通過jsch來連接遠(yuǎn)程服務(wù)器執(zhí)行l(wèi)inux命令
這篇文章主要介紹了Java中通過jsch來連接遠(yuǎn)程服務(wù)器執(zhí)行l(wèi)inux命令的相關(guān)資料,需要的朋友可以參考下2016-03-03Java基礎(chǔ)之SpringBoot整合knife4j
Swagger現(xiàn)在已經(jīng)成了最流行的接口文檔生成與管理工具,但是你是否在用的時候也在吐槽,它是真的不好看,接口測試的json數(shù)據(jù)沒法格式化,測試地址如果更改了還要去改配置,接口測試時增加token驗證是真的麻煩…針對Swagger的種種缺點,Knife4j就呼之欲出了.需要的朋友可以參考下2021-05-05