美文网首页
Java字符串拼接性能问题

Java字符串拼接性能问题

作者: 一个追寻者的故事 | 来源:发表于2020-05-20 11:22 被阅读0次

问题:如下2个方法 fun1fun2,都是在做同样的事情,手动拼接15000个A,哪个效率高?

    private static void func1(){
        long start = System.currentTimeMillis();
        String str = "";
        for (int i = 0; i < 15000; i++){
            str += "A";
        }
        long end = System.currentTimeMillis();
        System.out.println("time of function one: " + (end - start) + "ms");
    }

    private static void func2(){
        long start = System.currentTimeMillis();
        StringBuilder builder = new StringBuilder();
        for (int i = 0; i < 15000; i++){
            builder.append('A');
        }
        String str = builder.toString();
        long end = System.currentTimeMillis();
        System.out.println("time of function two: " + (end - start) + "ms");
    }

有编程经验的人都知道,肯定是使用了 StringBuilderfunc2 效率更高。先看一下运行结果:

time of function one: 206ms
time of function two: 1ms

我们今天就来探讨一下,为什么高,探讨这问题之前,我们先来看一下,下面这段代码如何执行的:

    public static void fun3() {
        String a = "A";
        a = a + "B";
    }

使用 javap 查看生成的字节码文件可知,在编译期间,自动转化成StringBuilder的实现方式。

  public static void fun3();
    descriptor: ()V
    flags: ACC_PUBLIC, ACC_STATIC
    Code:
      stack=2, locals=1, args_size=0
         0: ldc           #16                 // String A
         2: astore_0
         3: new           #3                  // class java/lang/StringBuilder
         6: dup
         7: invokespecial #4                  // Method java/lang/StringBuilder."<init>":()V
        10: aload_0
        11: invokevirtual #6                  // Method java/lang/StringBuilder.append:(Ljava/lang/String;)Ljava/lang/StringBuilder;
        14: ldc           #17                 // String B
        16: invokevirtual #6                  // Method java/lang/StringBuilder.append:(Ljava/lang/String;)Ljava/lang/StringBuilder;
        19: invokevirtual #8                  // Method java/lang/StringBuilder.toString:()Ljava/lang/String;
        22: astore_0
        23: return
      LocalVariableTable:
        Start  Length  Slot  Name   Signature
            3      21     0     a   Ljava/lang/String;

既然 + 已经默认转换成StringBuilder的方式,为何效率差距这么多呢?

具体看一下 fun1fun2 生成的字节码内容的区别(忽略了不重要的 system.out.println等信息):

public static void func1();
    descriptor: ()V
    flags: ACC_PUBLIC, ACC_STATIC
    Code:
      stack=2, locals=2, args_size=0
         0: ldc           #13                 // String
         2: astore_0
         3: iconst_0
         4: istore_1
         5: iload_1
         6: sipush        15000
         9: if_icmpge     38
        12: new           #3                  // class java/lang/StringBuilder
        15: dup
        16: invokespecial #4                  // Method java/lang/StringBuilder."<init>":()V
        19: aload_0
        20: invokevirtual #6                  // Method java/lang/StringBuilder.append:(Ljava/lang/String;)Ljava/lang/StringBuilder;
        23: ldc           #16                 // String A
        25: invokevirtual #6                  // Method java/lang/StringBuilder.append:(Ljava/lang/String;)Ljava/lang/StringBuilder;
        28: invokevirtual #8                  // Method java/lang/StringBuilder.toString:()Ljava/lang/String;
        31: astore_0
        32: iinc          1, 1
        35: goto          5
        38: return
      LocalVariableTable:
        Start  Length  Slot  Name   Signature
            5      33     1     i   I
            3      36     0   str   Ljava/lang/String;
public static void func2();
    descriptor: ()V
    flags: ACC_PUBLIC, ACC_STATIC
    Code:
      stack=2, locals=2, args_size=0
         0: new           #3                  // class java/lang/StringBuilder
         3: dup
         4: invokespecial #4                  // Method java/lang/StringBuilder."<init>":()V
         7: astore_0
         8: iconst_0
         9: istore_1
        10: iload_1
        11: sipush        15000
        14: if_icmpge     30
        17: aload_0
        18: bipush        65
        20: invokevirtual #14                 // Method java/lang/StringBuilder.append:(C)Ljava/lang/StringBuilder;
        23: pop
        24: iinc          1, 1
        27: goto          10
        30: aload_0
        31: invokevirtual #8                  // Method java/lang/StringBuilder.toString:()Ljava/lang/String;
        34: astore_1
        35: return
      LocalVariableTable:
        Start  Length  Slot  Name   Signature
           10      20     1     i   I
            8      28     0 builder   Ljava/lang/StringBuilder;
           35       1     1   str   Ljava/lang/String;

通过字节码可知:fun1 每次的循环,都会创建一个StringBuilder对象,循环结束时创建String对象(toString方法),大量对象创建肯定消耗性能,而且每次循环结束的话,也会伴有对象不会再引用的状态(StringBuilder、String),当然如果到一定量的话,还可能会触发垃圾收集。这些都是耗时的点。 而fun2的循环里,只有append方法的调用,并无对象的创建(如果不考虑StringBuilder 内 数组扩容的话)。

我们监视一下方法运行时的状态:-XX:+PrintGCDateStamps -XX:+PrintGCDetails -Xloggc:./gc.log

func1

Java HotSpot(TM) 64-Bit Server VM (25.191-b12) for bsd-amd64 JRE (1.8.0_191-b12), built on Oct  6 2018 08:37:07 by "java_re" with gcc 4.2.1 (Based on Apple Inc. build 5658) (LLVM build 2336.11.00)
Memory: 4k page, physical 8388608k(437048k free)

/proc/meminfo:

CommandLine flags: -XX:InitialHeapSize=134217728 -XX:MaxHeapSize=2147483648 -XX:+PrintGC -XX:+PrintGCDateStamps -XX:+PrintGCDetails -XX:+PrintGCTimeStamps -XX:+UseCompressedClassPointers -XX:+UseCompressedOops -XX:+UseParallelGC 
2020-05-20T11:10:57.339-0800: 0.145: [GC (Allocation Failure) [PSYoungGen: 33280K->448K(38400K)] 33280K->448K(125952K), 0.0031754 secs] [Times: user=0.00 sys=0.00, real=0.01 secs] 
2020-05-20T11:10:57.347-0800: 0.152: [GC (Allocation Failure) [PSYoungGen: 33728K->512K(71680K)] 33728K->520K(159232K), 0.0013694 secs] [Times: user=0.00 sys=0.00, real=0.00 secs] 
2020-05-20T11:10:57.368-0800: 0.173: [GC (Allocation Failure) [PSYoungGen: 67072K->448K(71680K)] 67080K->464K(159232K), 0.0013071 secs] [Times: user=0.00 sys=0.00, real=0.01 secs] 
2020-05-20T11:10:57.375-0800: 0.180: [GC (Allocation Failure) [PSYoungGen: 67008K->496K(138240K)] 67024K->520K(225792K), 0.0014076 secs] [Times: user=0.00 sys=0.00, real=0.00 secs] 
2020-05-20T11:10:57.413-0800: 0.219: [GC (Allocation Failure) [PSYoungGen: 133616K->517K(138240K)] 133640K->541K(225792K), 0.0017105 secs] [Times: user=0.00 sys=0.01, real=0.00 secs] 
2020-05-20T11:10:57.427-0800: 0.232: [GC (Allocation Failure) [PSYoungGen: 133637K->517K(267264K)] 133661K->549K(354816K), 0.0012731 secs] [Times: user=0.00 sys=0.00, real=0.00 secs] 
2020-05-20T11:10:57.511-0800: 0.317: [GC (Allocation Failure) [PSYoungGen: 266757K->32K(267264K)] 266789K->477K(354816K), 0.0017352 secs] [Times: user=0.00 sys=0.00, real=0.00 secs] 
Heap
 PSYoungGen      total 267264K, used 165463K [0x0000000795580000, 0x00000007b5f80000, 0x00000007c0000000)
  eden space 266240K, 62% used [0x0000000795580000,0x000000079f70ded8,0x00000007a5980000)
  from space 1024K, 3% used [0x00000007a5980000,0x00000007a5988000,0x00000007a5a80000)
  to   space 1024K, 0% used [0x00000007b5e80000,0x00000007b5e80000,0x00000007b5f80000)
 ParOldGen       total 87552K, used 445K [0x0000000740000000, 0x0000000745580000, 0x0000000795580000)
  object space 87552K, 0% used [0x0000000740000000,0x000000074006f498,0x0000000745580000)
 Metaspace       used 3084K, capacity 4500K, committed 4864K, reserved 1056768K
  class space    used 339K, capacity 388K, committed 512K, reserved 1048576K

func2

Java HotSpot(TM) 64-Bit Server VM (25.191-b12) for bsd-amd64 JRE (1.8.0_191-b12), built on Oct  6 2018 08:37:07 by "java_re" with gcc 4.2.1 (Based on Apple Inc. build 5658) (LLVM build 2336.11.00)
Memory: 4k page, physical 8388608k(251664k free)

/proc/meminfo:

CommandLine flags: -XX:InitialHeapSize=134217728 -XX:MaxHeapSize=2147483648 -XX:+PrintGC -XX:+PrintGCDateStamps -XX:+PrintGCDetails -XX:+PrintGCTimeStamps -XX:+UseCompressedClassPointers -XX:+UseCompressedOops -XX:+UseParallelGC 
Heap
 PSYoungGen      total 38400K, used 3329K [0x0000000795580000, 0x0000000798000000, 0x00000007c0000000)
  eden space 33280K, 10% used [0x0000000795580000,0x00000007958c0678,0x0000000797600000)
  from space 5120K, 0% used [0x0000000797b00000,0x0000000797b00000,0x0000000798000000)
  to   space 5120K, 0% used [0x0000000797600000,0x0000000797600000,0x0000000797b00000)
 ParOldGen       total 87552K, used 0K [0x0000000740000000, 0x0000000745580000, 0x0000000795580000)
  object space 87552K, 0% used [0x0000000740000000,0x0000000740000000,0x0000000745580000)
 Metaspace       used 3045K, capacity 4500K, committed 4864K, reserved 1056768K
  class space    used 333K, capacity 388K, committed 512K, reserved 1048576K

从虚拟机日志里验证了我们的想法,func1 不仅占用大量内存,而且期间还触发了 GC。到此,基本上说明白了为什么 fun2 效率更高。

相关文章

  • Java字符串拼接性能问题

    问题:如下2个方法 fun1 和 fun2,都是在做同样的事情,手动拼接15000个A,哪个效率高? 有编程经验的...

  • go语言string之Buffer与Builder

    操作字符串离不开字符串的拼接,但是Go中string是只读类型,大量字符串的拼接会造成性能问题。 字符串拼接的方式...

  • Java 字符串拼接效率分析及最佳实践

    转载请注明出处: Java 字符串拼接效率分析及最佳实践 本文来源于问题 Java字符串连接最佳实践? java连...

  • 日志、字符串拼接

    关于字符串拼接效率: 单词调用字符串拼接性能 + > strings.Join > bytes.Buffer > ...

  • Java String + 拼接空字符串性能问题

    开发过程中,遇到的问题,特此记录一下,也想着分享出来,免得不了解的朋友疑惑。 我们都知道 java String ...

  • 字符串

    一、字符串格式化 String.format()的使用 二、字符串拼接 字符串拼接性能比较 三、日志打印效率 使用...

  • C# string 拼接操作性能测试

    一、C# 拼接字符串的几种方式和性能 对于少量固定的字符串拼接,如string s= "a" + "b" + "c...

  • 同事如此使用StringBuilder,我给他提了一个Bug

    字符串的拼接在项目中使用的非常频繁,但稍不留意往往又会造成一些性能问题。 字符串的拼接在项目中使用的非常频繁,但稍...

  • StringBuilder性能优化

    在String字符串拼接性能优化博客中我已经介绍过了String "+" 拼接背后的StringBuilder实现...

  • 关于字符串

    字符串有不可变的特点,这样会带来一个问题:当我们大量拼接字符串时,会有性能问题。 字符方法:1、字符方法 2、截取...

网友评论

      本文标题:Java字符串拼接性能问题

      本文链接:https://www.haomeiwen.com/subject/gxlgohtx.html