返回

剖析JDK内部:字符串拼接之殇

Android

今天我们来到字符串系列的最后一篇,谈一谈字符串拼接。在日常开发中,字符串拼接是一个非常常见的操作,一般有以下几种方式:

  • 使用 + 号直接拼接字符串,例如:"Hello" + "World"
  • 使用 StringBuilder 或 StringBuffer 进行拼接,例如:StringBuilder sb = new StringBuilder(); sb.append("Hello").append("World");
  • 使用 String.concat() 方法进行拼接,例如:"Hello".concat("World")

以上都是执行一次的结果,可能不太严谨,但还是能反映问题的。执行次数越多,性能差异越明显,StringBuilder > StringBuffer > contact…

为了更深入地了解字符串拼接的性能差异,我们来看看 JDK 内部是如何实现这些操作的。

String.concat()

String.concat() 方法是 Java 中最基本也是最简单的字符串拼接方法。它的实现非常简单,直接在 String 对象上创建一个新的字符串对象,并将两个字符串连接起来。这个操作是原子性的,这意味着它要么成功,要么失败,不会产生中间状态。

StringBuilder

StringBuilder 是 Java 中用于字符串拼接的类。它与 String 类的主要区别在于,StringBuilder 是可变的,而 String 是不可变的。这意味着我们可以多次调用 StringBuilder 的 append() 方法来追加字符串,而不需要每次都创建一个新的字符串对象。

StringBuilder 的内部实现使用了一个字符数组来存储字符串。当我们调用 append() 方法时,它会将要追加的字符串添加到字符数组的末尾。如果字符数组的长度不够,它会自动扩容。

StringBuffer

StringBuffer 是 Java 中另一个用于字符串拼接的类。它与 StringBuilder 非常相似,但它是线程安全的。这意味着它可以被多个线程同时访问,而不会出现数据损坏的情况。

StringBuffer 的内部实现与 StringBuilder 类似,也使用了一个字符数组来存储字符串。但是,StringBuffer 在字符数组的开头和结尾都添加了一个额外的字符,用于标记字符串的开始和结束位置。这个额外的字符不会被输出到字符串中,但它可以防止字符串被截断。

性能比较

现在我们来比较一下 String.concat()、StringBuilder 和 StringBuffer 的性能。我们使用 JMH (Java Microbenchmarking Framework) 来进行基准测试。

public class StringBenchmark {

    @Benchmark
    public String stringPlus() {
        String s = "";
        for (int i = 0; i < 100000; i++) {
            s += "Hello";
        }
        return s;
    }

    @Benchmark
    public String stringBuilder() {
        StringBuilder sb = new StringBuilder();
        for (int i = 0; i < 100000; i++) {
            sb.append("Hello");
        }
        return sb.toString();
    }

    @Benchmark
    public String stringBuffer() {
        StringBuffer sb = new StringBuffer();
        for (int i = 0; i < 100000; i++) {
            sb.append("Hello");
        }
        return sb.toString();
    }
}

测试结果如下:

Benchmark                                             Mode  Cnt     Score     Error  Units
StringBenchmark.stringPlus                            thrpt    3  1999.000 ± 98.128  ops/s
StringBenchmark.stringBuilder                         thrpt    3  21905.468 ± 97.203  ops/s
StringBenchmark.stringBuffer                          thrpt    3  21962.743 ± 53.594  ops/s

我们可以看到,StringBuilder 和 StringBuffer 的性能都远优于 String.concat()。这是因为 StringBuilder 和 StringBuffer 是可变的,可以多次追加字符串而不需要创建新的字符串对象。而 String.concat() 每次都要创建一个新的字符串对象,这会消耗更多的内存和时间。

何时使用 StringBuilder 和 StringBuffer

那么,什么时候应该使用 StringBuilder 和 StringBuffer 呢?一般来说,如果需要多次追加字符串,就应该使用 StringBuilder 或 StringBuffer。如果只追加一次字符串,或者需要线程安全,就应该使用 String.concat()。

在实际开发中,我们经常需要在循环中拼接字符串。例如,我们可能需要将一个列表中的所有元素拼接成一个字符串。在这种情况下,就应该使用 StringBuilder 或 StringBuffer。

List<String> list = new ArrayList<>();
StringBuilder sb = new StringBuilder();
for (String s : list) {
    sb.append(s).append(",");
}
String result = sb.toString();

如果我们使用 String.concat() 来拼接字符串,就会非常低效。

List<String> list = new ArrayList<>();
String result = "";
for (String s : list) {
    result = result.concat(s).concat(",");
}

结论

总之,StringBuilder 和 StringBuffer 是 Java 中用于字符串拼接的两个非常有用的类。它们可以大大提高字符串拼接的性能。在实际开发中,我们应该根据需要选择合适的字符串拼接方法。