Java 中 String 和 StringBuffer 的区别

2025年8月4日 | 阅读 7 分钟

在 Java 中,String 是不可变的,这意味着一旦创建,我们就无法更改它。这使得它在频繁修改文本时效率较低。相反,StringBuffer 是可变的线程安全的,非常适合需要频繁更改的字符串,尤其是在多线程环境中。

Java 字符串

在 Java 中,String 用于表示字符序列,但它们在可变性以及处理修改的方式上存在显著差异。

Difference Between String and StringBuffer in Java

String 的特性

1. 不可变性: String 对象在 Java 中最关键的特性是它们是不可变的。这意味着当我们创建了一个 String 对象后,它的内容就不能被更改。

当对 String 执行诸如连接或子字符串之类的操作时,会创建一个全新的 String 对象,其中包含修改后的内容。然而,原始的 String 对象在内存中保持不变。

示例

它是如何工作的?

String 对象内部使用一个 char 数组来存储字符序列。由于它们是不可变的,这个 char 数组在创建后基本上是固定大小和内容的。

2. 线程安全性(同步): 由于 String 对象是不可变的,它们本质上是线程安全的。多个线程可以并发访问它们,而不会有数据不一致的风险,因为它们的状态无法更改。

3. String 性能: 对于涉及频繁修改的操作,String 通常效率较低。每次修改都会创建一个新的 String 对象。这会导致更多的内存分配和更多的垃圾回收工作。当字符串是常量或很少修改时,它是高效的。

4. 内存使用: String Pool 存储字符串字面量。如果已存在具有相同值的 String 对象,它会通过重用现有对象来优化内存使用。

使用 new String() 创建的 String 对象存储在通用堆内存中,不属于 String Pool。

5. 主要用例: 当一个字符序列是常量且不会被修改时,使用 String。非常适合表示固定文本,例如名称、标签或枚举值。它们在多线程环境中很有用,因为不可变性是安全性的优势。

String 示例

编译并运行

输出

Original String: Hello
Memory address (approx. hash): 622488023

After Concatenation (originalString + " World"): Hello World
Memory address of concatenatedString: 2124308362
Original String after concatenation: Hello
Memory address of originalString (still the same): 622488023

解释

当执行 originalString + " World" 时,会在内存中创建一个新的 String 对象 "Hello World"。originalString 本身仍然指向原始的 "Hello" 对象。

只有当我们重新赋值 originalString = originalString + " Java" 时,originalString 变量才会指向一个包含 "Hello Java" 的新 String 对象。如果没有任何其他引用指向它们,旧的 "Hello" 和 "Hello World" 对象可能会符合垃圾回收的条件。

注意:您在此程序中可能会得到不同的内存地址。

Java StringBuffer

在 Java 中,String 用于表示字符序列,但它们在可变性以及处理修改的方式上存在显著差异。

Difference Between String and StringBuffer in Java

StringBuffer 的特性

1. StringBuffer 可变: StringBuffer 对象是可变的。诸如追加、插入或删除字符之类的操作会修改内存中的现有对象,而不是创建新对象。

示例

它是如何工作的?

StringBuffer 使用内部 char 数组,允许动态扩展。当其当前容量不再足够时,会在内部创建一个新的、更大的数组,并将字符数据传输过去,从而确保在同一个 StringBuffer 对象内进行高效的内容修改。如图所示,StringBuffer 对象包含长度(当前大小)和容量(分配的空间),允许它增长。

2. 线程安全:一次只有一个线程可以访问 StringBuffer 实例的方法。它可以在多线程环境中防止数据损坏。这会带来性能上的开销。

3. StringBuffer 的性能: 对于涉及频繁修改的场景,StringBuffer 比 String 更高效。由于它修改现有对象,因此避免了创建许多新对象的开销。

4. 然而,由于其同步特性,在单线程环境中它比 StringBuilder 略慢,因为有获取和释放锁的开销。

5. StringBuffer 的内存使用: StringBuffer 对象始终存储在通用堆内存中。它们不使用 String Pool。

6. StringBuffer 的主要用例:当字符序列需要频繁修改且需要线程安全时使用。

StringBuffer 示例

编译并运行

输出

Original StringBuffer: Hello
Memory address (approx. hash): 622488023
After append(" World"): Hello World
Memory address of sb (still the same): 622488023
After insert(6, "Beautiful "): Hello Beautiful World
Memory address of sb (still the same): 622488023

解释

在所有修改操作(追加、插入、删除、反转)中,StringBuffer 对象 sbuff 本身会被直接修改。我们可以观察到它的 System.identityHashCode()(内存地址的大致表示)在这些操作中保持不变,这表明对于这些修改,堆中没有创建新对象。只有当我们显式调用 sbuff.toString() 时,才会根据 StringBuffer 的当前内容创建一个新的 String 对象。

注意:您在此程序中可能会得到不同的内存地址。

String 与 StringBuffer 的对比

特性StringStringBuffer
可变性不可变(创建后无法更改)可变(创建后可以更改)
可变(创建后可以更改)线程安全(因不可变性而固有)线程安全(由于所有公共修改方法的同步方法)
性能对于频繁修改操作速度较慢(创建新对象)对于频繁修改操作比 String 快;由于同步开销比 StringBuilder 慢
内存字面量使用 String Pool;使用 new 创建的对象使用通用堆使用通用堆内存;不使用 String Pool
用途用于常量字符串、不可变数据或预期修改很少的情况用于可变字符串,特别是在需要线程安全的可能性がある多线程环境中

String 的用法

  • 我们有不会更改或很少更改的文本。
  • 我们处理的字符串连接或操作数量很少,创建新 String 对象的开销可以忽略不计。
  • 我们优先考虑不可变性和固有的线程安全性。

StringBuffer 的用法

  • 我们需要对字符串执行频繁的修改(插入、删除、追加)。
  • 应用程序在多线程环境中运行,其中字符串操作的线程安全是一个关键问题。

结论

String 和 StringBuffer 的主要区别对于有效的 Java 字符序列处理至关重要。String 对象是不可变的,这使得它们天然线程安全,并且非常适合常量文本。相比之下,对 String 进行频繁修改会导致性能开销,因为每次操作都会创建一个新对象。

String 和 StringBuffer 选择题

1) Java 中 String 对象的主要特性是其 __________,这意味着一旦创建,其内容就不能被更改。

  1. 可变的
  2. volatile
  3. 不可变的
  4. 动态的
 

答案:c)

解释:不可变意味着“不可更改”。一旦用特定值创建了一个 String 对象,该值就不能被更改。任何看似修改 String 的操作实际上都会创建一个新的 String 对象。


2) 当一个 String 对象被修改时(例如,通过连接),会在内存中创建一个 __________ String 对象。

  1. new
  2. 临时的
  3. 缓存的
  4. 共享的
 

答案:a)

解释:由于 String 是不可变的,任何“修改”,如连接 (+) 操作,都不会改变原始对象。相反,会生成一个全新的 String 对象,其中包含组合内容。


3) StringBuffer 对象是 _________ 的,允许 append() 和 insert() 等操作修改内存中的现有对象。

  1. 不可变的
  2. static
  3. 可变的
  4. 固定大小
 

答案:c)

解释:可变意味着“可更改”。StringBuffer 对象可以在创建后进行修改。它们的内部字符数组可以扩展并直接修改,避免了每次更改都创建新对象的开销。


4) StringBuffer 的 __________ 是内部缓冲区可容纳的字符总数,在需要调整大小时可以增长,而无需创建新的对象实例。

  1. 可变
  2. 缓存
  3. 容量
  4. String
 

答案:c)

解释:容量是指 StringBuffer 的内部缓冲区在需要自行调整大小时可以容纳的总字符数。长度是当前使用的字符数,而容量是分配的总空间。


5) 调用 StringBuffer 对象的 __________ 方法将从其当前内容创建一个新的 String 对象。

  1. String Pool
  2. 静态
  3. toString()
  4. 动态
 

答案:c)

解释:StringBuffer 的 toString() 方法用于将可变的 StringBuffer 内容转换为不可变的 String 对象。调用此方法时,会在堆中实例化一个全新的 String 对象。