Java 中 String、StringBuilder、StringBuffer 的区别,这三者都是用于处理字符串的核心类,但在可变性、线程安全、性能等方面存在本质差异,下面从核心维度、详细对比、适用场景等方面全面解析:
一、核心维度对比(核心差异)
1. 可变性:不可变 vs 可变
-
String :
不可变字符序列。底层基于final char[](JDK 9 后改为final byte[])实现,字符串一旦创建,其内容(字符序列)和长度无法修改。任何看似修改String的操作(如拼接+、替换replace),本质都是创建新的String对象,原对象不会被改变。示例:String str = "Hello"; str += " World"; // 不是修改原对象,而是创建新的 String 对象 System.out.println(str); // 输出:Hello World -
StringBuilder :
可变字符序列。底层是可扩容的char[](非 final),修改操作(拼接、插入、删除等)直接在原有数组上进行,不会创建新对象,仅在容量不足时触发扩容。 -
StringBuffer :
可变字符序列。底层实现与StringBuilder一致,同样支持直接修改原有字符数组,无需创建新对象。
2. 线程安全性:线程安全 vs 非线程安全
-
String:天生线程安全。由于其不可变性,多线程并发访问时,不存在 "一个线程修改、另一个线程读取" 的并发问题,无需额外同步机制。
-
StringBuffer :
线程安全。其所有核心方法(如append()、insert())都通过synchronized关键字修饰,实现了方法级同步,保证多线程并发修改时的数据一致性,但同步机制会带来性能损耗。源码示例(StringBuffer 的 append 方法):@Override public synchronized StringBuffer append(String str) { toStringCache = null; super.append(str); return this; } -
StringBuilder :
非线程安全。其方法未提供任何同步机制,多线程并发修改同一个StringBuilder对象时,可能导致字符序列错乱、数据不一致等并发问题,但也因此避免了同步开销,性能更高。
3. 性能:高低差异显著
性能排序(从高到低):StringBuilder > StringBuffer > String
- String :性能最低。频繁修改(如循环拼接)时,会不断创建新的
String对象,产生大量无用对象(会被垃圾回收器回收),同时涉及频繁的内存拷贝,严重影响性能。 - StringBuffer :性能中等。由于
synchronized同步锁的开销,其性能略低于StringBuilder,但远高于String。 - StringBuilder:性能最高。无同步锁开销,所有修改操作直接在原有数组上执行,是单线程场景下字符串修改的最优选择。
二、其他细节对比
| 特性 | String | StringBuffer | StringBuilder |
|---|---|---|---|
| 可变性 | 不可变字符序列 | 可变字符序列 | 可变字符序列 |
| 线程安全 | 线程安全(不可变) | 线程安全(synchronized) | 非线程安全 |
| 性能 | 最低(频繁创建新对象) | 中等(同步开销) | 最高(无同步开销) |
| 初始容量 | 无(基于字符串内容) | 默认 16,支持指定 | 默认 16,支持指定 |
| 扩容机制 | 无(创建新对象) | 旧容量 * 2+2 | 旧容量 * 2+2 |
| 核心方法 | 无修改方法(仅查询) | append/insert/delete | append/insert/delete |
| 适用场景 | 字符串不修改 / 少量修改 | 多线程并发修改字符串 | 单线程大量修改字符串 |
三、关键补充说明
-
String 的 "+" 操作优化 :Java 编译器会对
String的连续拼接(如str = a + b + c)进行优化,底层自动转换为StringBuilder的append操作,避免多次创建对象。但在循环中拼接(如for循环内str += i),编译器无法优化,仍会频繁创建String对象,性能极差。示例(循环拼接对比):// 性能差:循环内 String 拼接 String str = ""; for (int i = 0; i < 1000; i++) { str += i; // 每次创建新 String 对象 } // 性能好:循环内 StringBuilder 拼接 StringBuilder sb = new StringBuilder(); for (int i = 0; i < 1000; i++) { sb.append(i); // 直接修改原有数组 } -
StringBuffer 的适用场景减少 :随着并发编程框架的普及(如
ConcurrentHashMap、Atomic类),StringBuffer的使用场景逐渐减少。多线程场景下,更多采用 "线程局部的StringBuilder+ 最终合并" 的方式,比直接使用StringBuffer性能更高。 -
三者的转换 :
String转StringBuilder/StringBuffer:通过构造方法(new StringBuilder(str));StringBuilder/StringBuffer转String:通过toString()方法。
四、适用场景总结
-
优先使用 String:
-
字符串内容固定不变(如常量定义、配置项、字符串比较);
-
仅需少量字符串拼接(非循环场景);
-
作为方法参数 / 返回值,无需修改的场景。示例:
// 常量定义(String 最优)
public static final String USER_NAME = "admin";
// 少量拼接(编译器自动优化)
String info = "姓名:" + "张三" + ",年龄:" + 25;
-
-
优先使用 StringBuilder:
-
单线程环境下;
-
大量字符串修改操作(如循环拼接日志、动态生成 SQL、拼接 JSON 字符串);
-
追求高性能的场景。示例:
// 单线程循环拼接日志(StringBuilder 最优)
StringBuilder logBuilder = new StringBuilder(1000); // 指定初始容量,减少扩容
for (int i = 0; i < 1000; i++) {
logBuilder.append("日志ID:").append(i).append(",时间:").append(System.currentTimeMillis()).append("\n");
}
String log = logBuilder.toString();
-
-
谨慎使用 StringBuffer:
-
多线程并发环境下,必须保证字符串修改的线程安全;
-
对性能要求不高,且修改操作较少的场景。示例:
// 多线程共享的字符串拼接(StringBuffer 安全)
public class Test {
private static StringBuffer sb = new StringBuffer();public static void main(String[] args) throws InterruptedException { Thread t1 = new Thread(() -> { for (int i = 0; i < 500; i++) { sb.append("a"); } }); Thread t2 = new Thread(() -> { for (int i = 0; i < 500; i++) { sb.append("b"); } }); t1.start(); t2.start(); t1.join(); t2.join(); System.out.println(sb.length()); // 输出:1000(安全) }}
-
五、核心总结
- 可变性是基础 :
String不可变,StringBuilder/StringBuffer可变,这是三者性能差异的根源; - 线程安全是关键区分 :
StringBuffer线程安全(同步锁),StringBuilder非线程安全,String天生安全; - 性能选择有优先级 :单线程大量修改用
StringBuilder,多线程用StringBuffer,字符串不变用String; - 避免误区 :循环内不要用
String的+拼接,优先使用StringBuilder并指定初始容量以提升性能。
