真实面经题目 · 原创解析
StringBuffer的append和String的+=区别?
StringBuffer 的 append 是在同一个可变字符缓冲区上追加内容;String 的 += 表面像修改字符串,实质通常是生成新的字符串结果。核心区别在于 String 不可变,+= 的优化依赖编译器和上下文,循环拼接容易产生大量临时对象;StringBuffer 维护可变缓冲区,append 多次追加时复用内部数组,并通过同步方法提供线程安全,但也带来锁开销。
真实面经题目 · 原创解析
StringBuffer 的 append 是在同一个可变字符缓冲区上追加内容;String 的 += 表面像修改字符串,实质通常是生成新的字符串结果。核心区别在于 String 不可变,+= 的优化依赖编译器和上下文,循环拼接容易产生大量临时对象;StringBuffer 维护可变缓冲区,append 多次追加时复用内部数组,并通过同步方法提供线程安全,但也带来锁开销。
可以这样回答:String 是不可变的,使用 += 时不会在原 String 对象上原地追加,而是把旧内容和新内容拼接成一个新的 String。对于常量拼接,编译器可能在编译期直接合并;对于运行期变量拼接,编译器通常会在单条拼接表达式中使用 StringBuilder 生成结果,但如果写在循环里,每次循环仍可能创建新的构建器和新的 String,成本较高。StringBuffer 的 append 则是操作一个可变字符缓冲区,多次追加时尽量复用内部数组,容量不够再扩容,所以适合大量动态拼接。StringBuffer 的方法带 synchronized,线程安全但性能通常低于 StringBuilder;单线程优先用 StringBuilder,多线程共享同一个缓冲区时才考虑 StringBuffer。
String 的 += 是字符串拼接语法,结果仍然是新的 String 对象;StringBuffer 的 append 是对可变缓冲区追加内容,当前缓冲对象自身会变化。也就是说,String 强调不可变值,StringBuffer 强调可变容器。
String 一旦创建,其字符内容不能被修改。变量 s 只是引用,执行 s += x 时,改变的是 s 指向的新对象,而不是原对象的内容。不可变带来的好处是安全、可缓存、便于作为 HashMap 的 key,也有利于字符串常量池;代价是在频繁拼接时可能产生很多中间对象。
如果拼接的都是字面量常量,例如常量字符串相加,编译器可以在编译期直接合并成一个常量结果,不会产生运行期拼接成本。如果拼接包含运行期变量,编译器通常会把单条表达式转换为类似 StringBuilder 的构建过程,最后调用 toString 得到新的 String。但这种优化通常局限于一条表达式,不等于循环中的 += 会自动复用同一个构建器。
在循环中写 s += item,每轮都要基于旧字符串生成新字符串。随着 s 变长,每次复制的字符越来越多,总体成本可能接近平方级增长,并伴随大量临时对象创建和垃圾回收压力。数据量小时差异不明显;数据量一大,循环 += 往往成为性能问题。
StringBuffer 内部维护可变字符缓冲区,append 会把新内容写入缓冲区末尾,并更新长度。只要容量足够,追加过程不需要每次生成完整的新字符串。只有在容量不够时才触发扩容,把旧内容复制到更大的数组中。最终需要不可变结果时,再通过 toString 生成 String。
StringBuffer 和 StringBuilder 都不是无限容量容器,它们有内部容量。追加内容超过容量时会扩容,扩容需要分配新数组并复制已有字符,因此也有成本。实际开发中,如果大致知道最终长度,可以在构造时传入初始容量,减少扩容次数,提高大量拼接时的稳定性。
StringBuffer 的 append 等关键方法带同步控制,多个线程共享同一个 StringBuffer 时可以避免内部状态被并发破坏。但同步会带来额外开销。StringBuilder 与 StringBuffer 的可变缓冲区思路相似,但不保证线程安全,因此单线程或局部变量场景下通常优先使用 StringBuilder。
少量简单拼接可以直接用 + 或 +=,可读性更好。循环、大量动态拼接、日志组装前的复杂字符串构造,优先使用 StringBuilder。确实存在多个线程共享同一个可变拼接对象时,可以考虑 StringBuffer,但更常见的做法是避免共享可变对象,让每个线程使用自己的 StringBuilder 或通过更清晰的并发设计处理。
因为 String 内容不可变,拼接后的内容不能写回原对象,只能创建一个包含新内容的 String,并让变量引用这个新结果。原对象如果没有其他引用,后续会等待垃圾回收。
因为编译器通常优化的是单次表达式。例如一次 s = s + x 可以变成临时构建器拼接后再 toString,但循环每轮都会执行一次表达式,仍可能每轮创建新的构建器和新的 String,无法像手写一个外部 StringBuilder 那样跨多轮复用。
单线程、方法局部变量、没有共享状态时选 StringBuilder,性能更好。多个线程必须共享同一个拼接缓冲区,并且需要同步保护内部状态时选 StringBuffer。实际业务里也可以通过不共享可变对象来避免使用 StringBuffer。
不是。append 追加基本类型时可能直接转换写入缓冲区,追加对象时会调用其字符串表示;容量不足会扩容并分配新数组;最后调用 toString 也会生成 String。它的优势不是零对象创建,而是避免每次追加都生成完整的新字符串。
不需要。少量拼接时 + 或 += 通常更清晰,编译器也能处理得很好。只有在循环、大量动态内容、性能敏感路径中,才更应该主动使用 StringBuilder 或在需要线程安全时使用 StringBuffer。