What (本质区别)
| 特性 | String | StringBuffer | StringBuilder |
|---|---|---|---|
| 可变性 | ❌ 不可变(final修饰) | ✅ 可变 | ✅ 可变 |
| 线程安全 | ✅ 天然线程安全(不可变) | ✅ 同步方法(synchronized) | ❌ 非同步(无锁) |
| JVM存储 | 常量池(字面量)/堆(new) | 堆内存(char[]数组) | 堆内存(char[]数组) |
| 适用场景 | 少量字符串操作、常量 | 多线程环境下的字符串拼接 | 单线程环境下的字符串拼接 |
💡 通俗比喻:
String → 刻在石头上的字(要改只能重刻一块石头)
StringBuffer → 带锁的记事本(多个人用,每次只能一个人写,安全但慢)
StringBuilder → 无锁的记事本(一个人用,想写就写,快但不安全)
JVM → 图书管理员(为不同场景提供合适的工具)
How (机制深度解析)
jvm实现机制
// String:每次操作都创建新对象
String str = "hello"; // 常量池
str += " world"; // 创建新String对象,原对象不变
// StringBuffer:内部char[]扩容 + 同步锁
public synchronized StringBuffer append(String str) {
toStringCache = null; // 清除缓存
super.append(str); // 调用父类方法
return this;
}
// StringBuilder:无锁版本,直接操作char[]
@Override
public StringBuilder append(String str) {
super.append(str); // 无synchronized修饰
return this;
}
性能对比 (100万字符拼接解析)
| 类型 | 内存占用 | GC次数 | 原因 |
|---|---|---|---|
| String | ~400MB | 25次 | 每次拼接创建新对象,旧对象成为垃圾 |
| StringBuffer | ~2MB | 1次 | 内部char[]动态扩容,对象复用 |
| StringBuilder | ~2MB | 1次 | 同StringBuffer,但无同步开销 |
💡 关键机制解释:
动态扩容:像伸缩水管,空间不够时自动扩容(通常1.5倍或2倍)
同步锁开销:StringBuffer每个方法都加synchronized,像每次进门都要刷卡验证
逃逸分析:JVM对StringBuilder进行优化,可能将其分配在栈上而非堆上,避免GC
常量折叠:编译器将"a" + "b" + "c"优化为"abc",String也能高效
Why(选择策略)
性能优先级
// ✅ 最佳实践:单线程用StringBuilder
public String buildReport(List<String> data) {
StringBuilder sb = new StringBuilder(1024); // 预估大小
for (String item : data) {
sb.append(item).append("\n");
}
return sb.toString();
}
// ✅ 多线程用StringBuffer
private static final StringBuffer logBuffer = new StringBuffer();
public static synchronized void log(String message) {
logBuffer.append(LocalDateTime.now()).append(": ").append(message).append("\n");
}
// ✅ 少量操作用String(编译器会优化)
String message = "User: " + userName + ", Action: " + action; // 编译期优化为StringBuilder
选择决策树
jvm优化机会
- StringBuilder逃逸分析:JVM检测到StringBuilder不逃逸出方法,可能将其分配在栈上,完全避免GC
- StringBuffer锁消除:JVM检测到StringBuffer只在单线程使用,可能消除同步锁
- 编译期优化:
"a" + "b" + "c"在编译期直接合并为"abc",运行时零开销
常见错误
// ❌ 陷阱1:在循环中用String拼接
String result = "";
for (int i = 0; i < 10000; i++) {
result += i; // 每次创建新对象,性能灾难
}
// ❌ 陷阱2:多线程用StringBuilder
public class UnsafeCounter {
private StringBuilder count = new StringBuilder("0");
public void increment() {
count.append(", ").append(System.currentTimeMillis()); // 线程不安全!
}
}
// ✅ 修复1:循环用StringBuilder
StringBuilder sb = new StringBuilder(10000);
for (int i = 0; i < 10000; i++) {
sb.append(i);
}
// ✅ 修复2:多线程用StringBuffer或加锁
private final StringBuffer safeCount = new StringBuffer("0");
public synchronized void increment() {
safeCount.append(", ").append(System.currentTimeMillis());
}
💡核心总结
| 类型 | 核心价值 | 使用频率 | 记忆口诀 |
|---|---|---|---|
| String | 安全、简单、不可变 | ⭐⭐⭐⭐⭐ | "少拼少接,安全第一" |
| StringBuffer | 多线程安全 | ⭐⭐ | "多人共享,锁保平安" |
| StringBuilder | 单线程极致性能 | ⭐⭐⭐⭐ | "一人专用,快如闪电" |
JVM层面的真相
- String的"快":只在编译期常量折叠时快,运行时拼接最慢
- StringBuffer的"慢":主要是同步锁开销,无竞争时JVM会优化
- StringBuilder的"快":无锁 + JVM逃逸分析 + 栈上分配 = 接近C语言性能
一句话本质:
String是安全的基石,StringBuffer是线程安全的盾牌,StringBuilder是性能的利剑