乐趣区

关于java:Java代码中字符串拼接方式分析

本文研究的字符串拼接形式为以下 4 种:“+”号、StringBuilder、StringJoiner、String#join,比照剖析及探讨最佳实际。

论断

前面内容比拟干燥,所以先说论断:

  1. 本文研究的字符串拼接形式为以下 4 种:“+”号、StringBuilder、StringJoiner、String#join
  2. 在简略的字符串拼接场景中「如:”a” + “b” + “c”」,以上四种形式性能无显著差别。
  3. 在循环字符串拼接的场景下,应用“+”号性能最低,其余三种形式性能也无显著差别,然而依据验证后果可浅显发现,指定初始容量的 StringBuilder 效率最高。当然不光思考性能,也要思考垃圾回收效率的问题,防止 OOM。
  4. 本文最初补充比照了 StringBuffer,在无争抢共享资源的场景下,StringBuffer 性能并未显著变差。

最佳实际

  1. 阿里巴巴 Java 开发手册 - 日志规约「5」可进行优化:应用占位符的模式可读性、便捷性不佳,可思考应用 Lambda,提早字符串的拼接,且应用更加便当。
  2. 阿里巴巴 Java 开发手册 -OOP 规约「23」可进行优化:循环拼接时须应用 StringBuilder;在拼接大量的大容量字符串时,应用 StringBuilder 尽量指定初始容量。
  3. 简略的字符串拼接可用任意形式,举荐间接应用“+”号拼接,可读性最优。
  4. 尽量应用 JDK 等间接提供的个性「如“+”号拼接字符串,Synchronized 关键词等」,因为编译器 +JVM 会继续对此进行优化,JDK 降级即可取得更大的收益。除非有明确的理由能够自行实现相似的性能。
  5. 在须要思考线程平安的场景能够思考应用 StringBuffer 进行字符串拼接,不过一般来说没有这种需要,故不应该应用 StringBuffer,防止减少复杂性。

剖析过程

环境

  1. 零碎: windows 10 21H1
  2. JDK: OpenJDK 1.8.0_302
  3. 剖析用示例代码:
@Slf4j
public 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
  1. 屡次测试,可发现在字符串循环拼接场景下,间接应用“+”号性能最低,有初始容量的 StringBuilder 性能最高,其余形式性能均没有太大差别。
  2. 屡次测试,可发现在字符串简略拼接场景下,应用“+”号、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
退出移动版