StringBuilderStringBuffer 是 Java 中用于处理可变字符串的两个核心类。它们功能几乎完全相同,但在线程安全性能上存在关键差异。


一、核心概念

1. 共同点

  • 可变字符序列:两者都实现了 AppendableCharSequence 接口,支持动态修改字符串内容。
  • 内部结构:都基于可变的 char[] 数组实现,支持自动扩容。
  • 功能一致:提供 appendinsertdeletereplacereversetoString 等相同的方法集。
  • 非 final 类:都可以被继承(尽管通常不建议)。

2. 核心区别

特性 StringBuilder StringBuffer
线程安全 ❌ 不安全 ✅ 安全
同步机制 无同步 方法使用 synchronized 关键字
性能 ⚡ 更高(单线程) ⚠️ 较低(因同步开销)
出现版本 JDK 1.5 JDK 1.0
适用场景 单线程环境 多线程共享环境

关键结论:在单线程下,优先使用 StringBuilder;只有在多线程需要共享同一个字符串缓冲区时,才使用 StringBuffer


二、操作步骤(两者通用,非常详细)

由于 StringBuilderStringBuffer 的 API 完全相同,以下操作步骤适用于两者。

步骤 1:导入类

// 两者都在 java.lang 包中,无需显式导入
// 但为了清晰,可写:
import java.lang.StringBuilder;
import java.lang.StringBuffer;

步骤 2:创建实例

方法 1:无参构造函数

StringBuilder sb = new StringBuilder();
StringBuffer sf = new StringBuffer();
// 初始容量:16 个字符

方法 2:指定初始容量

StringBuilder sb = new StringBuilder(100);
StringBuffer sf = new StringBuffer(100);
// 初始容量为 100,避免频繁扩容

方法 3:基于字符串初始化

StringBuilder sb = new StringBuilder("Hello");
StringBuffer sf = new StringBuffer("Hello");
// 初始内容为 "Hello",容量 = 16 + 字符串长度

步骤 3:追加内容(append

基本追加

sb.append("World");
sf.append("World");
// 支持:String, int, boolean, char, double, Object 等

链式调用(推荐)

sb.append("Name: ").append("Alice").append(", Age: ").append(30);
sf.append("Name: ").append("Bob").append(", Age: ").append(25);

步骤 4:插入内容(insert

sb.insert(5, " ");
sf.insert(5, " ");
// 在索引 5 处插入空格
// 注意:索引从 0 开始,不能越界

步骤 5:删除内容

删除指定范围

sb.delete(5, 6); // 删除索引 5 到 5([5,6))
sf.delete(5, 6);

删除单个字符

sb.deleteCharAt(5);
sf.deleteCharAt(5);

步骤 6:替换内容(replace

sb.replace(6, 11, "Java");
sf.replace(6, 11, "Java");
// 将索引 6 到 10 的字符替换为 "Java"

步骤 7:反转字符串(reverse

sb.reverse();
sf.reverse();
// 原地反转字符序列

步骤 8:获取字符串结果(toString

String result1 = sb.toString();
String result2 = sf.toString();
// 重要:返回新的 String 对象,原缓冲区仍可修改

步骤 9:查询信息

int len = sb.length();     // 当前字符数
int cap = sb.capacity();   // 当前数组容量
char c = sb.charAt(0);     // 获取索引 0 的字符
sb.setCharAt(0, 'h');      // 设置索引 0 的字符

三、常见错误

错误 1:混淆线程安全性

// 错误:在多线程中共享 StringBuilder
StringBuilder sharedSb = new StringBuilder();
// 多个线程同时执行 sharedSb.append(...) → 数据错乱!

// 正确:多线程应使用 StringBuffer
StringBuffer sharedSf = new StringBuffer();
// 或使用 StringBuilder + 外部同步
synchronized(sharedSb) {
    sharedSb.append("data");
}

错误 2:忽略 toString() 返回新对象

StringBuilder sb = new StringBuilder("Hello");
String str = sb.toString();
sb.append(" World");
// 此时 str 仍是 "Hello",不会变为 "Hello World"
// 因为 toString() 返回的是快照

错误 3:索引越界

StringBuilder sb = new StringBuilder("Hi");
sb.delete(10, 12); // 抛出 StringIndexOutOfBoundsException
// 解决:检查 length(),确保 start < end <= length()

错误 4:过度依赖自动扩容

StringBuilder sb = new StringBuilder();
for (int i = 0; i < 10000; i++) {
    sb.append("a");
}
// 可能触发多次数组复制(扩容)
// 优化:new StringBuilder(10000)

四、注意事项

  1. 线程安全是核心区别

    • StringBuffer 的每个修改方法(如 appendinsert)都加了 synchronized,保证多线程安全。
    • StringBuilder 无同步,单线程下性能更高。
  2. 性能差异来源

    • synchronized 会带来锁竞争、上下文切换等开销。
    • 在单线程下,StringBuilder 通常比 StringBuffer 快 10%-20%。
  3. 容量管理

    • 初始容量 16。
    • 扩容策略:newCapacity = (oldCapacity << 1) + 2(即 old * 2 + 2)。
    • 频繁扩容会触发数组复制,影响性能。
  4. toString() 的实现

    • 两者都通过 new String(value, 0, count) 创建新字符串,不共享内部数组。
  5. 不要用于多线程缓存

    • 即使使用 StringBuffer,在高并发下仍可能成为瓶颈。考虑 ThreadLocal<StringBuilder> 或无锁设计。

五、使用技巧

技巧 1:根据场景选择类

// 场景 1:单线程拼接(如方法内)
StringBuilder sb = new StringBuilder();
sb.append("Processing...").append(id);

// 场景 2:多线程共享(罕见)
StringBuffer sf = new StringBuffer();
// 多个线程调用 sf.append(...)

技巧 2:预设容量提升性能

// 预估最终长度 ≈ 1000
StringBuilder sb = new StringBuilder(1000);
// 避免扩容,提升 10%-30% 性能

技巧 3:ThreadLocal 优化多线程

// 高并发下,避免共享 StringBuffer
private static final ThreadLocal<StringBuilder> tlSb = 
    ThreadLocal.withInitial(() -> new StringBuilder(1000));

// 使用
StringBuilder sb = tlSb.get();
sb.setLength(0); // 清空复用
sb.append("data");
String result = sb.toString();

技巧 4:链式调用提高可读性

return new StringBuilder()
    .append("{")
    .append("\"name\":\"").append(name).append("\",")
    .append("\"age\":").append(age)
    .append("}")
    .toString();

技巧 5:清空内容复用对象

sb.setLength(0); // 清空,复用对象(比 new 更快)
// 或 sb.delete(0, sb.length());

六、最佳实践与性能优化

1. 选择原则

场景 推荐类
单线程,频繁拼接 StringBuilder
多线程共享拼接 StringBuffer
多线程,非共享 ThreadLocal<StringBuilder>
简单拼接(<3次) String +(编译器优化)
构建 JSON/XML StringBuilder + 预设容量

2. 性能对比测试(示例)

// 单线程性能测试
long start = System.nanoTime();
StringBuilder sb = new StringBuilder();
for (int i = 0; i < 10000; i++) sb.append("a");
long timeSb = System.nanoTime() - start;

start = System.nanoTime();
StringBuffer sf = new StringBuffer();
for (int i = 0; i < 10000; i++) sf.append("a");
long timeSf = System.nanoTime() - start;

System.out.println("StringBuilder: " + timeSb);
System.out.println("StringBuffer: " + timeSf);
// 通常 StringBuilder 更快

3. 避免 StringBuffer 的陷阱

  • 不要滥用:99% 的字符串拼接发生在单线程方法内,用 StringBuilder 即可。
  • 高并发瓶颈synchronized 方法在高并发下可能成为性能瓶颈。

4. 结合其他工具

  • StringJoiner:JDK 8+,适合分隔符拼接。
    StringJoiner sj = new StringJoiner(",");
    sj.add("a").add("b");
    
  • String.format:格式化字符串,但性能低于 StringBuilder 链式调用。

5. 监控与调优

  • 使用 JMH 测试实际性能。
  • 监控 GC 频率,频繁字符串操作可能导致 Young GC 增加。
  • 考虑对象池(谨慎使用,可能增加复杂度)。

总结

对比项 StringBuilder StringBuffer
线程安全
性能
推荐场景 单线程(99%) 多线程共享(1%)
关键方法 append, insert, delete, toString 同左
最佳实践 预设容量、链式调用、复用 仅在必要时使用

最终建议

默认使用 StringBuilder。只有当你明确需要在多个线程间共享同一个可变字符串缓冲区时,才使用 StringBuffer。在绝大多数应用中,StringBuilder 是更优、更高效的选择。