理解三者在可变性、线程安全性和性能上的本质区别
掌握在不同场景下如何正确选择合适的字符串类
能通过代码验证三者的实际行为差异
避免因误用导致的性能问题或并发错误
关键理解:
String的“安全”源于不可变性,而非同步机制
StringBuilder和StringBuffer共享几乎相同的 API,唯一区别是线程安全性
public class CoderHub {
public static void main(String[] args) {
String str = "Hello";
str.concat(" World"); // 返回新对象,但未赋值
System.out.println(str); // 输出: Hello
// 正确使用方式(但效率低)
str = str.concat(" World");
System.out.println(str); // Hello World
}
}📌
concat()、+、substring()等操作均返回新String对象,原对象不变。
StringBuilder sb = new StringBuilder("Hello");
sb.append(" World"); // 直接修改内部 char[]
System.out.println(sb); // Hello World✅ 所有操作原地完成,无新对象产生。
StringBuffer sb = new StringBuffer("Hello");
sb.append(" World"); // 方法声明为 synchronized
System.out.println(sb); // Hello World🔒 每个公共方法都加了
synchronized锁,确保多线程下操作原子性。
String?字符串内容不会改变(如配置常量、字面量)
作为键(Key) 存入 HashMap(依赖不可变性保证哈希一致性)
简单的、少量的字符串拼接(编译器会优化为 StringBuilder)
// 推荐
final String APP_NAME = "CoderHub";
String message = "User " + userId + " logged in."; // 编译期优化StringBuilder?单线程环境下的高频字符串操作(如日志拼接、JSON 构建)
循环中动态构建字符串
性能敏感的文本处理
// 推荐:循环拼接
StringBuilder sb = new StringBuilder();
for (int i = 0; i < 1000; i++) {
sb.append("Item ").append(i).append("\n");
}
String result = sb.toString();StringBuffer?多线程共享同一个可变字符串对象
需要线程安全且无法使用其他同步机制的场景
// 多线程日志收集器(简化示例)
public class ThreadSafeLogger {
private final StringBuffer log = new StringBuffer();
public void addLog(String msg) {
log.append(Thread.currentThread().getName())
.append(": ").append(msg).append("\n");
}
public String getLog() {
return log.toString();
}
}注意:现代开发中,更推荐使用
java.util.concurrent工具或局部StringBuilder+ 合并结果,而非直接共享StringBuffer。
假设执行 10,000 次字符串追加:
实际测试中,
StringBuilder在单线程下通常比StringBuffer快 20%~50%。
❌ 误区 1:“StringBuffer 比 StringBuilder 功能更强”
事实:两者 API 完全一致,唯一区别是同步。
❌ 误区 2:“String 拼接总是低效”
事实:编译器会对编译期确定的拼接优化为
StringBuilder:String s = "A" + "B" + "C"; // 编译为 "ABC"(常量折叠) String s = "Hello " + name; // 编译为 new StringBuilder(...).toString()
❌ 误区 3:“多线程必须用 StringBuffer”
事实:若每个线程使用独立的 StringBuilder,再合并结果,性能更高且线程安全。
String:内容固定 → 安全、简洁、适合常量
StringBuilder:单线程高频修改 → 首选,性能最优
StringBuffer:多线程共享可变字符串 → 线程安全,但有性能代价
黄金法则:默认用
String,拼接用StringBuilder,仅在明确需要线程安全时才用StringBuffer。
为什么 Java 设计 String 为不可变类?这种设计对 JVM 内存管理(如字符串常量池)有何影响?
编写一个多线程程序,分别使用 StringBuilder 和 StringBuffer 进行共享字符串追加,观察 StringBuilder 可能出现的数据错乱现象。
在 Android 或 Spring Boot 项目中,你更可能在哪里看到 StringBuilder 的使用?为什么这些场景不适合用 String?