高 基础
String-StringBuilder-StringBuffer区别#
一句话答案#
String 不可变每次拼接创建新对象,StringBuilder 可变非线程安全(推荐),StringBuffer 可变线程安全(synchronized)。
核心要点
| 类 | 可变 | 线程安全 | 性能 |
|---|---|---|---|
| String | 不可变 | 安全(不可变) | 拼接慢(创建新对象) |
| StringBuilder | 可变 | 不安全 | 快(推荐) |
| StringBuffer | 可变 | 安全(synchronized) | 较慢 |
面试回答(2分钟版)
三者的核心区别在于可变性和线程安全性。String 是不可变的,底层的 char 数组被 final 修饰且没有暴露修改方法,每次拼接都会创建新的 String 对象,在循环拼接场景下性能很差。StringBuilder 是可变的,在同一个 char 数组上直接修改,初始容量 16,不够时按旧容量乘以二加二扩容,不需要每次创建新对象,性能最好,但线程不安全。StringBuffer 和 StringBuilder 功能一样,区别是所有方法都加了 synchronized 保证线程安全,但因为加锁有额外开销所以性能稍差。实际开发中绝大多数字符串操作都在单线程环境下,所以优先使用 StringBuilder。有一个常见的坑是在循环中用加号拼接字符串,虽然编译器会优化为 StringBuilder,但每次循环都会创建新的 StringBuilder 对象,正确做法是在循环外面创建一个 StringBuilder 循环内 append。JDK 9 之后 String 拼接的底层改用了 invokedynamic 加 StringConcatFactory,性能有进一步提升。
追问与易错
追问方向:
- “String 拼接底层怎么实现的?”→ JDK8 编译器将 + 拼接转换为 StringBuilder.append();JDK9+ 改用 invokedynamic + StringConcatFactory,由 JVM 运行时选择最优拼接策略
- “StringBuilder 初始容量是多少?”→ 默认初始容量 16 个字符,扩容公式为旧容量 * 2 + 2,通过 Arrays.copyOf 复制到新数组
- “什么场景下 String 拼接性能也可以?”→ 编译期常量折叠:纯字面量拼接如 “a” + “b” 在编译时直接合并为 “ab”,不产生运行时开销;单次非循环拼接编译器也会优化
易错点:
- ❌ “循环中用 + 拼接没问题”——每次循环创建新 StringBuilder,应在循环外创建
- ❌ “StringBuffer 比 StringBuilder 安全所以优先用”——大部分场景不需要线程安全