本文研究的字符串拼接形式为以下4种:“+”号、StringBuilder、StringJoiner、String#join,比照剖析及探讨最佳实际。
论断
前面内容比拟干燥,所以先说论断:
- 本文研究的字符串拼接形式为以下4种:“+”号、StringBuilder、StringJoiner、String#join
- 在简略的字符串拼接场景中「如:"a" + "b" + "c"」,以上四种形式性能无显著差别。
- 在循环字符串拼接的场景下,应用“+”号性能最低,其余三种形式性能也无显著差别,然而依据验证后果可浅显发现,指定初始容量的StringBuilder效率最高。当然不光思考性能,也要思考垃圾回收效率的问题,防止OOM。
- 本文最初补充比照了StringBuffer,在无争抢共享资源的场景下,StringBuffer性能并未显著变差。
最佳实际
- 阿里巴巴Java开发手册-日志规约「5」可进行优化:应用占位符的模式可读性、便捷性不佳,可思考应用Lambda,提早字符串的拼接,且应用更加便当。
- 阿里巴巴Java开发手册-OOP 规约「23」可进行优化:循环拼接时须应用StringBuilder;在拼接大量的大容量字符串时,应用StringBuilder尽量指定初始容量。
- 简略的字符串拼接可用任意形式,举荐间接应用“+”号拼接,可读性最优。
- 尽量应用JDK等间接提供的个性「如“+”号拼接字符串,Synchronized关键词等」,因为编译器+JVM会继续对此进行优化,JDK降级即可取得更大的收益。除非有明确的理由能够自行实现相似的性能。
- 在须要思考线程平安的场景能够思考应用StringBuffer进行字符串拼接,不过一般来说没有这种需要,故不应该应用StringBuffer,防止减少复杂性。
剖析过程
环境
- 零碎: windows 10 21H1
- JDK: OpenJDK 1.8.0_302
- 剖析用示例代码:
@Slf4jpublic class StringConcat { @SneakyThrows public static void main(String[] args) { log.info("java虚拟机预热开始"); 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虚拟机预热完结"); 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(); }}
后果及总结
- java虚拟机预热开始- java虚拟机预热完结- 开始测试:- loop-plus: 执行超时- loop-stringBuilderCapacity: 285- loop-stringBuilder: 1968- loop-String.join: 1313- loop-stringJoiner: 1238- simple-Plus: 812- simple-StringBuilder: 840- simple-StringBuffer: 857
- 屡次测试,可发现在字符串循环拼接场景下,间接应用“+”号性能最低,有初始容量的StringBuilder性能最高,其余形式性能均没有太大差别。
- 屡次测试,可发现在字符串简略拼接场景下,应用“+”号、StringBuilder、StringBuffer性能差距在5%左右,可了解为测试误差,可认为三种形式性能统一。
代码及后果剖析
1. StringBuilder与StringBuffer比照
在无争抢共享资源的场景下,JVM会应用偏差锁等办法优化,甚至会进行锁打消,应用Synchronized关键词与否,性能并无显著差别。
2. 字节码剖析
比照上述#simplePlus和#simpleStringBuilder两个办法的字节码,可显著看到两办法执行内容基本一致,然而间接应用"+"号时解决流程更短,可见编译器进行了深度优化,应用优化后的字节码实践上会有更高的性能:
// 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