1. 概述

在Java中处理字符串时,StringStringBuffer 是两个重要的类。简单来说,字符串是一系列字符的序列,例如 "java"、"spring" 等。

StringStringBuffer 的主要区别在于,String 是不可变的,而 StringBuffer 是可变且线程安全的。

本教程将比较这两个类,并理解它们之间的相似性和差异。

2. String

String 类表示字符字符串。Java将所有像 "baeldung" 这样的字符串字面值实现为这个类的一个实例。

创建一个字符串字面量:

String str = "baeldung";

同样,我们也可以创建一个 String 对象:

Char data[] = {‘b’, ‘a’, ‘e’, ‘l’, ‘d’, ‘u’, ‘n’, ‘g’};

String str = new String(data);

我们还可以进行如下操作:

String str = new String(“baeldung”);

字符串是常量且不可变的,这使得它们可以共享。

2.1. 字符串字面量与字符串对象

字符串字面量是不可变的字符串,它们存储在堆内存中的一个特殊区域,称为字符串池。对于具有相同值的字符串字面量,Java不会分配新的内存空间。相反,它使用字符串合并(interning)。

相比之下,JVM会在堆内存中(字符串池之外)为新创建的 String 对象分配单独的内存。

因此,每个字符串对象都有不同的内存地址,尽管它们可能具有相同的值。请注意,字符串字面量仍然是一个 String 对象,但反之则不然。

2.2. 字符串池

字符串字面量存储在Java堆中的预留内存区域,称为字符串池。

2.3. 字符串合并(String Interning)

字符串合并是编译器用来避免重复内存分配的一种优化技术。如果已经有相似的值存在,它会避免为新的字符串字面量分配内存。相反,它会使用已存在的副本:

字符串内存分配

String 的常见操作包括连接、比较和搜索。Java语言还提供了对字符串连接运算符 (+) 和将其他对象转换为字符串的特殊支持。值得注意的是,String 内部使用 StringBuffer 和其 append 方法来执行连接:

String str = "String"; 
str = str.concat("Buffer");
assertThat(str).isEqualTo("StringBuffer");
assertThat(str.indexOf("Buffer")).isEqualTo(6);

3. StringBuffer

StringBuffer 也是一个字符序列,就像 String 一样。然而,与 String 不同,它是可变的。我们可以通过 append()insert() 等方法修改 StringBufferappend 方法在 StringBuffer 的末尾添加字符序列,而 insert 方法在指定索引处插入字符序列。StringBuffer 类有重载的方法来处理任何对象,先将对象转换为其字符串表示形式,然后再追加或插入到 StringBuffer 中:

StringBuffer sBuf = new StringBuffer("String");
sBuf.append("Buffer");
assertThat(sBuf).isEqualToIgnoringCase("StringBuffer");
sBuf.insert(0, "String vs ");
assertThat(sBuf).isEqualToIgnoringCase("String vs StringBuffer");

StringBuffer 是线程安全的,可以在多线程环境中工作。同步确保了所有语句的正确执行顺序,避免了数据竞争的情况。

然而,Java 1.5引入了 StringBuilder,作为在不需要考虑线程安全性的场景下的性能优化替代 StringBuffer。如果字符串缓冲区被单个线程使用,建议使用 StringBuilder,因为它在大多数实现下通常更快。

4. 性能比较

StringStringBuffer 的性能相似。但是,由于 String 每次修改都需要创建一个新的对象,所有的更改都会针对新创建的 String 进行,这会导致更多的时间和内存消耗。

让我们用 JMH 进行一个快速的微基准测试,比较 StringStringBuffer 的连接性能:

@BenchmarkMode(Mode.SingleShotTime)
@OutputTimeUnit(TimeUnit.MILLISECONDS)
@Measurement(batchSize = 100000, iterations = 10)
@Warmup(batchSize = 100000, iterations = 10)
@State(Scope.Thread)
public class ComparePerformance {

    String strInitial = "springframework";
    String strFinal = "";
    String replacement = "java-";

    @Benchmark
    public String benchmarkStringConcatenation() {
        strFinal = "";
        strFinal += strInitial;
        return strFinal;
    }

    @Benchmark
    public StringBuffer benchmarkStringBufferConcatenation() {
        StringBuffer stringBuffer = new StringBuffer(strFinal);
        stringBuffer.append(strInitial);
        return stringBuffer;
    }
}
Benchmark                                              Mode  Cnt   Score    Error  Units
ComparePerformance.benchmarkStringBufferConcatenation    ss   10  16.047 ± 11.757  ms/op
ComparePerformance.benchmarkStringConcatenation          ss   10   3.492 ±  1.309  ms/op

5. 比较表

总结一下差异:

String

StringBuffer

String 是一个字符序列,且不可变

StringBuffer 类似于 String,但可以修改,即它是可变的

由于不可变性,可以轻松共享

只能在同步线程之间共享

每次修改都需要创建一个新的字符串

需要调用特定方法进行修改

修改较慢

修改更快

使用字符串池存储数据

使用堆内存

6. 结论

在这篇文章中,我们比较了 StringStringBuffer 类。如往常一样,示例代码可在 GitHub 上找到。