Java 面试核心:String vs StringBuilder vs StringBuffer 深度解析
一、三者核心区别一览
| 特性 | String | StringBuilder | StringBuffer |
|---|---|---|---|
| 可变性 | 不可变 (Immutable) | 可变 (Mutable) | 可变 (Mutable) |
| 线程安全 | 安全(但无关,因为不可变) | 不安全 | 安全(synchronized) |
| 性能 | 低(每次操作创建新对象) | 高(单线程最快) | 中(同步带来开销) |
| 使用场景 | 字符串常量、少量操作 | 单线程大量字符串拼接 | 多线程大量字符串拼接 |
| JDK版本 | 1.0 | 1.5 | 1.0 |
二、String 的不可变性(Immutable)
为什么不可变?
java
// 典型面试代码
String s = "hello";
s = s + " world"; // 创建了新对象,s指向新引用,"hello"被GC
不可变的好处:
- 字符串常量池复用 --- 节省内存
- HashCode 缓存 --- 适合作为 HashMap 的 Key
- 线程安全 --- 天然并发安全
- 安全 --- 防止参数被篡改(如网络传输、文件路径)
经典面试题
Q:以下代码创建了几个对象?
javaString s1 = new String("abc");A:2个 --- 堆中的
new String()对象 + 常量池中的"abc"
Q:String.intern()的作用?
A: 将字符串放入常量池,返回常量池引用。用于手动控制字符串复用。
三、StringBuilder vs StringBuffer 源码级对比
1. 线程安全差异(核心考点)
java
// StringBuffer:方法加 synchronized
public synchronized StringBuffer append(String str) {
toStringCache = null;
super.append(str);
return this;
}
// StringBuilder:无同步,裸奔
public StringBuilder append(String str) {
super.append(str);
return this;
}
2. 扩容机制(两者相同)
java
// 默认初始容量 16,扩容:新容量 = 旧容量 × 2 + 2
void expandCapacity(int minimumCapacity) {
int newCapacity = value.length * 2 + 2;
// ...
}
面试建议: 预估长度时用 new StringBuilder(100) 避免频繁扩容。
四、性能对比实测
| 操作 | 耗时 | 说明 |
|---|---|---|
String + 循环10000次 |
很慢 | 创建大量临时对象 |
StringBuilder 单线程 |
最快 | 无锁,直接操作字符数组 |
StringBuffer 单线程 |
较慢 | 同步锁开销 |
StringBuffer 多线程 |
安全 | 正确性优先 |
结论: 单线程无脑用 StringBuilder,多线程才考虑 StringBuffer。
五、高频面试题精编
Q1:为什么 String 设计成不可变的?
答: 安全(防篡改)、高效(常量池复用)、线程安全、HashCode 缓存。
Q2:+ 操作符底层用什么?
答: JDK8+ 编译器自动转为
StringBuilder.append(),但循环中会每次新建 Builder,应手动用 Builder。
Q3:StringBuilder 为什么快?
答: 1)可变字符数组,原地修改;2)无同步锁开销;3)扩容时批量复制。
Q4:什么场景必须用 StringBuffer?
答: 多线程并发修改同一字符串对象时(实际很少见,通常用并发容器或局部变量)。
六、最佳实践速记
| 场景 | 推荐选择 |
|---|---|
| 字符串常量、配置项 | String |
| 单线程字符串拼接(循环、流处理) | StringBuilder ⭐ |
| 多线程共享可变字符串(罕见) | StringBuffer |
| 高并发日志、序列化 | StringBuilder(局部变量) |
七、总结
String 是不可变的常量,StringBuilder 是单线程的快刀,StringBuffer 是多线程的保险柜。现代 Java 开发,90% 场景用 StringBuilder,剩下 9% 用 String,1% 才用 StringBuffer。