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):- 当需要在循环中频繁连接字符串时(绝对首选)。
- 当需要多次修改一个字符串的内容(插入、删除、替换等)时。
- 当性能和内存效率是关键考虑因素,且修改操作很多时。
StringBuffervsStringBuilder: 需要线程安全时用StringBuffer;单线程环境下追求最高性能用StringBuilder(更常用)。
黄金法则:
- 永远不要在循环中使用
String的+或concat进行大量拼接!始终使用StringBuilder(或StringBuffer)。 - 对于简单的、单次的字符串连接,
String的+是可读且可接受的(编译器通常会优化)。 - 优先考虑代码可读性和正确性,在性能确实成为瓶颈时才进行优化。但在循环拼接这个特定场景下,从一开始就用
StringBuilder就是最佳实践。