StringBuffer线程安全而StringBuilder不安全,因前者方法加synchronized锁,后者无锁;多线程共享时StringBuffer防数据错乱,StringBuilder易出错;单线程优先用StringBuilder,性能高10%~15%。

StringBuffer 是线程安全的,StringBuilder 不是
这是最核心的区别。StringBuffer 的所有公开方法(如 append()、insert()、reverse())都加了 synchronized 关键字;StringBuilder 对应方法则完全没加锁。这意味着:在多线程环境下直接共享一个 StringBuffer 实例,不会出现数据错乱;而共享 StringBuilder 极大概率导致内容损坏或抛出 IndexOutOfBoundsException。
常见错误现象包括:拼接结果缺失部分字符串、长度计算错误、toString() 返回空或乱码。尤其在高并发日志拼接、HTTP 响应体组装等场景中容易暴露。
- 如果你的应用是单线程,或每个线程独占一个实例(比如局部变量),优先用 StringBuilder —— 它快 10%~15%
- 如果必须跨线程共享可变字符串(如静态缓存、全局格式化器),且无法改用不可变
String或锁外管理,才考虑 StringBuffer - 注意:线程安全 ≠ 线程友好。StringBuffer 的同步粒度是整个对象,高并发下会成为瓶颈
构造函数和扩容机制完全一致
两者继承自同一个抽象父类 AbstractStringBuilder,所以底层字符数组(char[] value)、初始容量(默认 16)、扩容逻辑(newCapacity = (oldCapacity )全部相同。性能差异只来自同步开销,不来自内存或算法。
这意味着:替换使用时无需调整初始化参数或预估容量。例如 new StringBuffer(128) 和 new StringBuilder(128) 行为完全一致,只是后者不锁。
立即学习“Java免费学习笔记(深入)”;
- 不要因为“StringBuilder 更快”就盲目调大初始容量——过大的
value数组反而浪费堆内存 - 若已知最终长度(比如拼接固定字段的 SQL),显式传入容量能避免多次扩容,这对两者都有效
API 几乎 100% 兼容,但不能互相转型
除了同步修饰符,StringBuffer 和 StringBuilder 的方法签名、返回类型、异常声明完全一样。你可以把 StringBuilder 的代码复制到 StringBuffer 中,编译零报错;反之亦然。
但它们没有继承关系,也不是同一接口实现,因此不能隐式转换或泛型通配。下面写法会编译失败:
StringBuilder sb = new StringBuilder(); StringBuffer sbf = (StringBuffer) sb; // ❌ 编译错误
如果需要抽象掉具体类型(比如封装工具类),只能靠提取公共方法或使用 CharSequence 接收参数(但会丢失 append() 等可变操作)。
- 避免在方法签名中直接使用 StringBuffer/ StringBuilder 作为参数类型,除非明确需要其可变性
- 对外提供 API 时,优先接受
String或CharSequence,内部再按需构建具体实现 - 单元测试里混用两者容易掩盖线程问题——测试通过不代表生产安全
现代 Java 中更推荐用 String.join() 或文本块替代手工拼接
Java 8 引入 String.join(),Java 15+ 支持文本块("""),很多原本需要 StringBuffer/StringBuilder 的场景其实可以规避可变字符串。
比如拼接路径、生成 JSON 片段、构造 SQL WHERE 条件等,用 String.join(", ", list) 或 STR."..."(字符串模板预览版)更简洁、不可变、天然线程安全。
- 只有当拼接逻辑复杂(含条件分支、循环嵌套、中间状态复用)时,才真正需要 StringBuilder
- Log4j / SLF4J 的参数化日志(
log.info("User {} logged in at {}", name, time))已内部优化,无需手动拼接 - 别为了“看起来高效”而提前优化——先写清楚逻辑,再看 profiler 数据是否真卡在字符串拼接上









