String拼接的性能对比

/ Java / 0 条评论 / 360 浏览

概要

所有人在学习Java的时候,都被老师强调,字符串相加要用StringBuffer或者StringBuild,表面理解确实会快些,但是经过编译优化后,还会快吗?今天借助Jad反编译工具,看一下优化后,字符串直接相加在不同场景会怎么样。

环境

JDK1.8

IDEA2018.2

jad 1.5.8g

场景

主要分为以下四个场景:

  1. 简单的直接相加
  2. 多次相加
  3. 循环相加
  4. 有判断的相加

简单相加

源码

public class StringTest {
  public static void main(String[] args) {
    // String 直接拼接
    String a = "1111";
    String b = "22222";
    String c = "3333";
    System.out.println(a + b + c);
  }
}

编译后

// Decompiled by Jad v1.5.8g. Copyright 2001 Pavel Kouznetsov.
// Jad home page: http://www.kpdus.com/jad.html
// Decompiler options: packimports(3) 
// Source File Name:   StringTest.java

import java.io.PrintStream;

public class StringTest
{

    public StringTest()
    {
    }

    public static void main(String args[])
    {
        String a = "1111";
        String b = "22222";
        String c = "3333";
        System.out.println((new StringBuilder()).append(a).append(b).append(c).toString());
    }
}

多次相加

源码

public class StringTest {
  public static void main(String[] args) {
    // String 直接拼接
    String a = "1111";
    String b = "22222";
    String c = "3333";
    System.out.println(a + b + c);
    a = a + b;
    System.out.println(a + c);
  }
}

编译后

// Decompiled by Jad v1.5.8g. Copyright 2001 Pavel Kouznetsov.
// Jad home page: http://www.kpdus.com/jad.html
// Decompiler options: packimports(3) 
// Source File Name:   StringTest.java

import java.io.PrintStream;

public class StringTest
{

    public StringTest()
    {
    }

    public static void main(String args[])
    {
        String a = "1111";
        String b = "22222";
        String c = "3333";
        System.out.println((new StringBuilder()).append(a).append(b).append(c).toString());
        a = (new StringBuilder()).append(a).append(b).toString();
        System.out.println((new StringBuilder()).append(a).append(c).toString());
    }
}

循环场景

源码

public class StringTest {

  public static void main(String[] args) {
    String a = "1111";
    String b = "22222";
    //循环字符串拼接
    for (int i = 0; i < 100; i++) {
      a = a + b;
    }
    System.out.println(a);
    //循环 buffer拼接
    StringBuffer forBuffer = new StringBuffer(a);
    for (int i = 0; i < 100; i++) {
      forBuffer.append(b);
    }
    System.out.println(forBuffer.toString());
  }
}

编译后

// Decompiled by Jad v1.5.8g. Copyright 2001 Pavel Kouznetsov.
// Jad home page: http://www.kpdus.com/jad.html
// Decompiler options: packimports(3) 
// Source File Name:   StringTest.java

import java.io.PrintStream;

public class StringTest
{

    public StringTest()
    {
    }

    public static void main(String args[])
    {
        String a = "1111";
        String b = "22222";
        for(int i = 0; i < 100; i++)
            a = (new StringBuilder()).append(a).append(b).toString();

        System.out.println(a);
        StringBuffer forBuffer = new StringBuffer(a);
        for(int i = 0; i < 100; i++)
            forBuffer.append(b);

        System.out.println(forBuffer.toString());
    }
}

有判断

源码

public class StringTest {

  public static void main(String[] args) {
    String a = "1111";
    String b = "22222";
    int i = 3;
    //字符串拼接
    if (i % 2 == 0) {
      a = a + b;
    }
    System.out.println(a);
    //buffer拼接
    StringBuffer forBuffer = new StringBuffer(a);
    if (i % 2 == 0) {
      forBuffer.append(b);
    }
    System.out.println(forBuffer.toString());
  }
}

编译后

// Decompiled by Jad v1.5.8g. Copyright 2001 Pavel Kouznetsov.
// Jad home page: http://www.kpdus.com/jad.html
// Decompiler options: packimports(3) 
// Source File Name:   StringTest.java

import java.io.PrintStream;

public class StringTest
{

    public StringTest()
    {
    }

    public static void main(String args[])
    {
        String a = "1111";
        String b = "22222";
        int i = 3;
        if(i % 2 == 0)
            a = (new StringBuilder()).append(a).append(b).toString();
        System.out.println(a);
        StringBuffer forBuffer = new StringBuffer(a);
        if(i % 2 == 0)
            forBuffer.append(b);
        System.out.println(forBuffer.toString());
    }
}

性能

各种场景下,看到了优化器的结果,那就试试10W字符循环拼接下的,直接相加,Buffer,Builder相加的性能表现。

public class StringTest {

  public static void main(String[] args) {
    String a = "1111";
    String b = "22222";
    //循环字符串拼接
    long startTime = System.currentTimeMillis();
    for (int i = 0; i < 100000; i++) {
      a = a + b;
    }
    System.out.println(System.currentTimeMillis() - startTime);
    //循环 buffer拼接
    StringBuffer forBuffer = new StringBuffer(a);
    startTime = System.currentTimeMillis();
    for (int i = 0; i < 100000; i++) {
      forBuffer.append(b);
    }
    System.out.println(System.currentTimeMillis() - startTime);
    //循环 buffer拼接
    StringBuilder forBuilder = new StringBuilder(a);
    startTime = System.currentTimeMillis();
    for (int i = 0; i < 100000; i++) {
      forBuilder.append(b);
    }
    System.out.println(System.currentTimeMillis() - startTime);
  }
}

该测试只代表单次结果,如果您的结果与我的不一致,请以您的为准

直接相加:53104ms

Buffer相加:4ms

Builder相加:3ms

结论

在简单字符串拼接中,字符串直接相加,优化器会把代码优化称Builder,无论相加几次,都会只有一个Builder。

在其他拼接场景中,优化器都没有很好的发挥作用,频繁创建Builder对象会严重影响性能。对性能要求较高的项目,可以使用根据线程是否安全自行选择Buffer或者Builder。