01
60 秒回答模板
StringBuffer 底层可以概括为带同步保护的可变字符缓冲区。它本身负责提供线程安全的可变字符串操作,底层主要逻辑由 AbstractStringBuilder 承担,包括内部存储数组、当前字符数 count、容量 capacity、append 时的容量检查和扩容。需要注意不要把它死答成永远是 char[]:在 JDK 8 及更早常见实现里可以按 char[] 理解;从 JDK 9 的紧凑字符串优化开始,String、StringBuilder、StringBuffer 相关实现会使用 byte[] 加 coder 来区分 Latin-1 和 UTF-16,以减少内存占用。无论内部是 char[] 还是 byte[],核心思想都是连续数组加动态扩容。append 时会先计算追加后的最小容量,如果超过当前 capacity,就扩容并复制旧内容,再写入新内容并更新 count。toString 会生成一个不可变 String,语义上是当前内容的快照,不能把可变缓冲区直接暴露出去。StringBuffer 和 StringBuilder 的底层可变机制很接近,主要区别是 StringBuffer 的很多方法带 synchronized,线程安全但有锁开销;StringBuilder 不做同步,单线程下更常用。String 则是不可变对象,拼接或修改语义通常会产生新对象。
考点 一句话定位
主线 核心父类
易错点 把 StringBuffer 说成底层链表,忽略它的核…
02
深入解析
01 一句话定位
StringBuffer 是线程安全的可变字符序列。它解决的是 String 不可变导致频繁拼接时对象创建较多的问题,通过维护一块可增长的内部缓冲区,在原对象上完成追加、插入、删除、替换等操作。
02 核心父类
在常见实现里,StringBuffer 的可变字符串能力并不是每个方法都从零实现,而是复用 AbstractStringBuilder 的底层逻辑。AbstractStringBuilder 负责保存内部数组、记录当前长度、处理容量、执行扩容和字符搬移,StringBuffer 主要是在这些操作外层增加同步控制。
03 底层存储
底层存储要按版本稳妥表达:早期常见实现可理解为 char[],每个字符按 UTF-16 代码单元存储;JDK 9+ 的紧凑字符串优化中,相关字符串类可能使用 byte[] 加 coder 标记,Latin-1 内容可用单字节存储,无法单字节表示时使用 UTF-16。答题时说可变的连续字符或字节数组缓冲区,比只说 char[] 更准确。
04 capacity 与 count
count 表示当前实际存了多少个字符,也就是 length 的语义;capacity 表示内部缓冲区当前最多能容纳多少字符。capacity 通常大于或等于 count,预留空间的目的就是让多次 append 不必每次都重新分配数组。
05 append 扩容流程
append 的关键步骤是先把要追加的内容转换成字符序列,再计算追加后的最小容量。如果最小容量超过现有 capacity,就创建更大的内部数组并复制旧内容;常见扩容策略接近旧容量乘 2 再加 2,但这是实现细节,不应当作为 Java 语义保证来死记。
06 toString 语义
toString 返回的是一个不可变 String,语义上代表调用时刻的内容。实现可能通过复制或缓存来优化,但必须保证后续修改 StringBuffer 不会破坏已经返回的 String 的不可变语义,所以不能理解成直接暴露原始可变数组。
07 线程安全边界
StringBuffer 的线程安全来自 synchronized,同一个实例上的关键方法会互斥执行,并保证方法级别的可见性和顺序性。但它并不自动保证多个方法组合起来的复合逻辑一定原子,例如先判断长度再追加仍可能需要调用方额外同步。
08 三者对比
StringBuffer 和 StringBuilder 都是可变字符串缓冲区,底层思路接近,差别主要在同步:StringBuffer 有 synchronized,StringBuilder 没有同步开销。String 则是不可变字符串,修改语义通常产生新对象,适合内容稳定、可共享、可作为常量或键值使用的场景。
03
易错点
- 把 StringBuffer 说成底层链表,忽略它的核心是连续数组缓冲区。
- 死记为 char[],完全不提 JDK 9+ 可能是 byte[] 加 coder 的版本差异。
- 只回答 synchronized,没解释 AbstractStringBuilder、count、capacity 和扩容。
- 把 capacity 当成 length,分不清容量和实际字符数。
- 误以为 append 每次都会创建新的 String,忽略可变缓冲区复用。
- 认为 StringBuffer 线程安全就能保证任意多步业务逻辑都原子。
- 把 toString 理解成直接返回内部可变数组,忽略 String 的不可变快照语义。
- 不区分 StringBuffer、StringBuilder、String 的使用场景,只背线程安全和不安全。
04
面试官追问
StringBuffer 为什么是线程安全的?
因为它的关键公开方法通常使用 synchronized 修饰,对同一个 StringBuffer 实例的操作会通过对象锁串行化。这样能保证单次方法调用内对内部数组、count、capacity 等状态的修改不会被其他线程同时打断。
StringBuffer 默认容量是多少?
无参创建时常见默认初始容量是 16 个字符。如果用已有字符串或字符序列初始化,通常会在已有长度基础上额外预留一部分容量,以减少后续追加时的扩容次数。
扩容一定是 2 倍加 2 吗?
常见实现的策略接近旧容量乘 2 再加 2,如果这个值仍小于所需最小容量,就直接使用所需最小容量。但这是实现细节,回答可以说常见扩容策略类似翻倍,并保证至少满足本次所需容量。
JDK 9 之后 StringBuffer 还是 char[] 吗?
不能简单说一定是 char[]。JDK 9 引入紧凑字符串后,String、StringBuilder、StringBuffer 相关内部表示可能使用 byte[] 加 coder,以 Latin-1 或 UTF-16 的形式存储;对外 API 的字符序列语义不变。
StringBuffer 和 StringBuilder 怎么选?
如果是单线程、方法内部局部拼接,通常选 StringBuilder,因为没有同步开销。如果多个线程确实共享同一个可变字符串对象,可以考虑 StringBuffer,但复杂复合操作仍可能需要更明确的外部同步设计。
StringBuffer 的 toString 会不会影响后续修改?
不会。toString 返回的是不可变 String,代表调用时刻的内容;后续继续 append 或 delete 不应改变已经得到的 String。具体实现可能复制或缓存,但语义上必须隔离可变缓冲区和不可变结果。