String与StringBuilder的区别
在 Java 中,String
和 StringBuilder
(以及它的线程安全版本 StringBuffer
) 是处理字符串的核心类,它们的关键区别在于 可变性 及其带来的性能影响。以下是主要区别:
不可变性 vs 可变性
String
(不可变):String
对象一旦创建,其内容就不能被修改。任何看似修改String
的操作(如concat()
,+
,replace()
,substring()
,toUpperCase()
等),实际上都会创建一个全新的String
对象在内存中。原始字符串保持不变。StringBuilder
(可变):StringBuilder
对象代表一个可变的字符序列。你可以通过其方法(如append()
,insert()
,delete()
,replace()
,reverse()
等)直接修改其内部字符数组的内容,而不会创建新的对象(除非内部数组需要扩容)。
性能 (尤其是在循环或频繁修改时)
String
: 由于不可变性,在循环或需要大量拼接/修改字符串的场景下(例如在一个循环中反复使用+=
或concat()
),会产生大量临时的、中间态的String
对象。创建和销毁这些对象会带来显著的内存分配和垃圾回收开销,导致性能低下。StringBuilder
: 由于直接在现有缓冲区上修改,避免了创建大量临时对象。它在进行大量字符串连接或修改操作时性能显著优于String
。这是它存在的主要原因。
内存使用
String
: 频繁修改会导致内存中存在许多不再使用的中间字符串对象,等待垃圾回收。这会占用更多内存,并可能增加 GC 停顿时间。StringBuilder
: 通常更节省内存,尤其是在大量修改操作中,因为它主要操作一个可扩展的缓冲区。虽然内部数组在需要时会扩容(创建新数组并复制数据),但这比创建大量完整的新字符串对象开销小得多。
线程安全性
String
: 由于其不可变性,String
对象本质上是线程安全的。多个线程可以安全地读取同一个String
对象。StringBuilder
: 不是线程安全的。它的方法没有同步机制。如果多个线程需要同时修改同一个StringBuilder
实例,可能会导致数据不一致。如果需要线程安全,应使用StringBuffer
(其方法使用synchronized
关键字修饰)。
API 和初始化
String
: 通常通过双引号"Hello"
字面量创建(存储在字符串常量池),或通过new String(...)
创建(在堆上)。提供丰富的字符串操作功能,但都返回新字符串。StringBuilder
: 必须通过new StringBuilder()
或new StringBuilder(int capacity)
或new StringBuilder(String str)
创建。提供专注于修改缓冲区内容的方法(append
,insert
,delete
,replace
,setLength
,reverse
等)。
equals()
和hashCode()
行为String
: 重写了equals()
和hashCode()
方法,基于字符串的内容进行比较。StringBuilder
: 没有重写Object
的equals()
和hashCode()
方法。比较两个StringBuilder
对象是基于对象引用(内存地址),而不是内容。要比较内容,需要先调用toString()
转换为String
再比较。
总结表格
特性 | String | StringBuilder |
---|---|---|
可变性 | 不可变 | 可变 |
修改操作 | 创建新对象 | 修改现有对象 |
性能 (修改) | 差 (大量对象创建/GC) | 优 (原地修改) |
内存开销 | 高 (大量中间对象) | 低 (一个缓冲区) |
线程安全 | 是 (因不可变) | 否 (用 StringBuffer 替代) |
初始化 | 字面量 或 new String(...) | new StringBuilder(...) |
equals/hash | 基于内容 | 基于对象引用 |
主要用途 | 表示固定不变的文本 | 高效构建或修改字符串 (拼接、替换等) |
何时使用哪个?
使用
String
:- 当字符串的值在创建后不会改变时(例如,常量、配置信息、键值)。
- 需要依赖内容比较 (
equals
) 或哈希 (hashCode
) 时。 - 需要线程安全地共享字符串时(读取是安全的)。
- 简单的、少量的字符串连接(编译器有时会优化
+
为StringBuilder
,但不要在循环中依赖此优化!)。
使用
StringBuilder
(或StringBuffer
):- 当需要在循环中频繁连接字符串时(绝对首选)。
- 当需要多次修改一个字符串的内容(插入、删除、替换等)时。
- 当性能和内存效率是关键考虑因素,且修改操作很多时。
StringBuffer
vsStringBuilder
: 需要线程安全时用StringBuffer
;单线程环境下追求最高性能用StringBuilder
(更常用)。
黄金法则:
- 永远不要在循环中使用
String
的+
或concat
进行大量拼接!始终使用StringBuilder
(或StringBuffer
)。 - 对于简单的、单次的字符串连接,
String
的+
是可读且可接受的(编译器通常会优化)。 - 优先考虑代码可读性和正确性,在性能确实成为瓶颈时才进行优化。但在循环拼接这个特定场景下,从一开始就用
StringBuilder
就是最佳实践。