真实面经题目 · 原创解析

StringBuffer的append和String的+=区别?

StringBuffer 的 append 是在同一个可变字符缓冲区上追加内容;String 的 += 表面像修改字符串,实质通常是生成新的字符串结果。核心区别在于 String 不可变,+= 的优化依赖编译器和上下文,循环拼接容易产生大量临时对象;StringBuffer 维护可变缓冲区,append 多次追加时复用内部数组,并通过同步方法提供线程安全,但也带来锁开销。

出现于:阿里巴巴 · 算法

60 秒回答模板

可以这样回答:String 是不可变的,使用 += 时不会在原 String 对象上原地追加,而是把旧内容和新内容拼接成一个新的 String。对于常量拼接,编译器可能在编译期直接合并;对于运行期变量拼接,编译器通常会在单条拼接表达式中使用 StringBuilder 生成结果,但如果写在循环里,每次循环仍可能创建新的构建器和新的 String,成本较高。StringBuffer 的 append 则是操作一个可变字符缓冲区,多次追加时尽量复用内部数组,容量不够再扩容,所以适合大量动态拼接。StringBuffer 的方法带 synchronized,线程安全但性能通常低于 StringBuilder;单线程优先用 StringBuilder,多线程共享同一个缓冲区时才考虑 StringBuffer。

考点 本质区别
主线 String 不可变
易错点 误以为 s += x 会修改原 String 对象。

深入解析

01

本质区别

String 的 += 是字符串拼接语法,结果仍然是新的 String 对象;StringBuffer 的 append 是对可变缓冲区追加内容,当前缓冲对象自身会变化。也就是说,String 强调不可变值,StringBuffer 强调可变容器。

02

String 不可变

String 一旦创建,其字符内容不能被修改。变量 s 只是引用,执行 s += x 时,改变的是 s 指向的新对象,而不是原对象的内容。不可变带来的好处是安全、可缓存、便于作为 HashMap 的 key,也有利于字符串常量池;代价是在频繁拼接时可能产生很多中间对象。

03

+= 的编译期和运行期差异

如果拼接的都是字面量常量,例如常量字符串相加,编译器可以在编译期直接合并成一个常量结果,不会产生运行期拼接成本。如果拼接包含运行期变量,编译器通常会把单条表达式转换为类似 StringBuilder 的构建过程,最后调用 toString 得到新的 String。但这种优化通常局限于一条表达式,不等于循环中的 += 会自动复用同一个构建器。

04

循环拼接成本

在循环中写 s += item,每轮都要基于旧字符串生成新字符串。随着 s 变长,每次复制的字符越来越多,总体成本可能接近平方级增长,并伴随大量临时对象创建和垃圾回收压力。数据量小时差异不明显;数据量一大,循环 += 往往成为性能问题。

05

append 的缓冲区模型

StringBuffer 内部维护可变字符缓冲区,append 会把新内容写入缓冲区末尾,并更新长度。只要容量足够,追加过程不需要每次生成完整的新字符串。只有在容量不够时才触发扩容,把旧内容复制到更大的数组中。最终需要不可变结果时,再通过 toString 生成 String。

06

扩容机制

StringBuffer 和 StringBuilder 都不是无限容量容器,它们有内部容量。追加内容超过容量时会扩容,扩容需要分配新数组并复制已有字符,因此也有成本。实际开发中,如果大致知道最终长度,可以在构造时传入初始容量,减少扩容次数,提高大量拼接时的稳定性。

07

线程安全差异

StringBuffer 的 append 等关键方法带同步控制,多个线程共享同一个 StringBuffer 时可以避免内部状态被并发破坏。但同步会带来额外开销。StringBuilder 与 StringBuffer 的可变缓冲区思路相似,但不保证线程安全,因此单线程或局部变量场景下通常优先使用 StringBuilder。

08

适用场景

少量简单拼接可以直接用 + 或 +=,可读性更好。循环、大量动态拼接、日志组装前的复杂字符串构造,优先使用 StringBuilder。确实存在多个线程共享同一个可变拼接对象时,可以考虑 StringBuffer,但更常见的做法是避免共享可变对象,让每个线程使用自己的 StringBuilder 或通过更清晰的并发设计处理。

易错点

  • 误以为 s += x 会修改原 String 对象。
  • 误以为所有 += 都会在编译期优化成一个常量。
  • 误以为编译器用了 StringBuilder 后,循环 += 就没有性能问题。
  • 误以为 StringBuffer 总是比 String 拼接快,忽略少量拼接时可读性和优化效果。
  • 误以为 StringBuffer 和 StringBuilder 只有名字不同,忽略线程安全和同步开销。
  • 误以为 append 永远不分配新内存,忽略扩容和最终 toString 的成本。
  • 在单线程局部拼接中盲目使用 StringBuffer,承担不必要的同步开销。

面试官追问

为什么说 String 的 += 会产生新对象?

因为 String 内容不可变,拼接后的内容不能写回原对象,只能创建一个包含新内容的 String,并让变量引用这个新结果。原对象如果没有其他引用,后续会等待垃圾回收。

既然编译器会把 + 优化成 StringBuilder,为什么循环里还不推荐 +=?

因为编译器通常优化的是单次表达式。例如一次 s = s + x 可以变成临时构建器拼接后再 toString,但循环每轮都会执行一次表达式,仍可能每轮创建新的构建器和新的 String,无法像手写一个外部 StringBuilder 那样跨多轮复用。

StringBuffer 和 StringBuilder 怎么选?

单线程、方法局部变量、没有共享状态时选 StringBuilder,性能更好。多个线程必须共享同一个拼接缓冲区,并且需要同步保护内部状态时选 StringBuffer。实际业务里也可以通过不共享可变对象来避免使用 StringBuffer。

append 一定不会创建对象吗?

不是。append 追加基本类型时可能直接转换写入缓冲区,追加对象时会调用其字符串表示;容量不足会扩容并分配新数组;最后调用 toString 也会生成 String。它的优势不是零对象创建,而是避免每次追加都生成完整的新字符串。

少量字符串拼接需要强行改成 append 吗?

不需要。少量拼接时 + 或 += 通常更清晰,编译器也能处理得很好。只有在循环、大量动态内容、性能敏感路径中,才更应该主动使用 StringBuilder 或在需要线程安全时使用 StringBuffer。