核心看三点:字符串是否需修改、是否多线程共享、性能要求。不改用string(常量、map key等);单线程拼接用stringbuilder(快10%–20%,需预设容量);仅当多线程真正共用同一实例时才用stringbuffer(方法级同步,但非万能)。

面试中问到 String、StringBuilder 和 StringBuffer 怎么选,核心就看三点:是否需要修改字符串、是否多线程共享、对性能有没有明确要求。
字符串不改,直接用 String
只要值确定后不再变化,比如常量、配置项、Map 的 key、方法参数接收的原始文本,一律优先用 String。它的不可变性带来天然线程安全和字符串常量池复用能力,还能避免意外篡改。例如:"HTTP_404"、"user.id"、jsonString.getBytes() 中传入的原始串,都不该换成可变类型。
单线程拼接,首选 StringBuilder
循环内拼日志、组装 SQL、生成 HTML 片段等场景,95% 以上都发生在单一线程里。此时 StringBuilder 没有 synchronized 开销,扩容策略也更激进,吞吐量比 StringBuffer 高 10%–20%。常见误写是写成 str += "a" ——这背后被编译器转成 StringBuilder,但每次循环都新建对象,反而更慢。正确写法是:
- 提前 new StringBuilder(预估容量),比如 new StringBuilder(256)
- 循环中只调 append(),最后统一 toString()
- 避免在循环外反复创建 StringBuilder 实例
多线程共用,才考虑 StringBuffer
真正需要多个线程同时往同一个字符串缓冲区追加内容的场景极少。比如全局统计日志收集器、共享的调试追踪上下文。这时 StringBuffer 的每个 public 方法都带 synchronized,能保证操作原子性。但要注意:它只是方法级同步,不能替代锁来保护复合逻辑(如“先查长度再 append”仍需额外同步)。如果并发度高,更推荐用 ThreadLocal
别踩这些坑
- StringBuffer 不等于“更老更好”:JDK 5 引入 StringBuilder 后,官方已明确建议单线程下优先用它
- 不要为“看起来线程安全”而滥用 StringBuffer:多数 Web 请求、Service 方法都是线程隔离的,用 StringBuilder 更合理
- concat、+、replace 等 String 方法返回新对象:频繁调用会触发大量 GC,监控中常表现为 old gen 增长快或 stop-the-world 时间升高
- toString() 是廉价操作:StringBuilder/StringBuffer 的 toString() 只是包装已有数组,不复制内容(JDK 17+ 优化后)
Java免费学习笔记:立即使用
解锁 Java 大师之旅:从入门到精通的终极指南











