StringBuilder系列

核心概念

Java提供了三种字符串类型:

  • String:不可变,适合少量操作
  • StringBuilder:可变,非线程安全,适合单线程大量拼接
  • StringBuffer:可变,线程安全,适合多线程场景

一、三者对比

1.1 核心区别

特性 String StringBuilder StringBuffer
可变性 不可变 可变 可变
线程安全 是(不可变) 是(synchronized)
性能 拼接慢 最快 较快
引入版本 JDK 1.0 JDK 1.5 JDK 1.0
使用场景 少量操作 单线程拼接 多线程拼接

1.2 性能对比

// 测试:拼接10万次
// String: 约5000ms(每次创建新对象)
// StringBuilder: 约5ms
// StringBuffer: 约10ms(同步开销)

// String拼接(慢)
String s = "";
for (int i = 0; i < 100000; i++) {
    s += i;  // 每次创建新对象
}

// StringBuilder拼接(快)
StringBuilder sb = new StringBuilder();
for (int i = 0; i < 100000; i++) {
    sb.append(i);  // 原地修改
}
String result = sb.toString();

1.3 线程安全对比

// StringBuffer:线程安全
public synchronized StringBuffer append(String str) {
    toStringCache = null;
    super.append(str);
    return this;
}

// StringBuilder:非线程安全
public StringBuilder append(String str) {
    super.append(str);
    return this;
}

二、StringBuilder底层实现

2.1 内部结构

abstract class AbstractStringBuilder {
    byte[] value;  // 存储字符数据(JDK 9+)
    byte coder;    // 编码标识
    int count;     // 实际字符数

    // JDK 8及之前
    // char[] value;
}

2.2 构造方法

// 默认容量16
StringBuilder sb1 = new StringBuilder();  // capacity = 16

// 指定初始容量
StringBuilder sb2 = new StringBuilder(100);  // capacity = 100

// 根据字符串初始化
StringBuilder sb3 = new StringBuilder("hello");  // capacity = 5 + 16 = 21

2.3 扩容机制

// 扩容公式:newCapacity = (oldCapacity << 1) + 2
// 即:新容量 = 旧容量 * 2 + 2

private int newCapacity(int minCapacity) {
    int newCapacity = (value.length << 1) + 2;  // 2倍+2
    if (newCapacity - minCapacity < 0) {
        newCapacity = minCapacity;  // 如果还不够,直接用所需容量
    }
    return (newCapacity <= 0 || MAX_ARRAY_SIZE - newCapacity < 0)
        ? hugeCapacity(minCapacity)
        : newCapacity;
}

扩容过程示例:

StringBuilder sb = new StringBuilder();  // capacity = 16
sb.append("12345678901234567");  // 需要17,触发扩容
// 新容量 = 16 * 2 + 2 = 34

2.4 为什么扩容是2倍+2?

  • 2倍:平衡内存使用和扩容频率
  • +2:避免初始容量为0时无法扩容

三、常用方法

3.1 追加方法

StringBuilder sb = new StringBuilder();

// append():追加各种类型
sb.append("hello");
sb.append(123);
sb.append(true);
sb.append(3.14);

// 链式调用
sb.append("a").append("b").append("c");

3.2 插入方法

StringBuilder sb = new StringBuilder("hello");

sb.insert(0, "say ");  // "say hello"
sb.insert(4, "123");   // "say 123hello"

3.3 删除方法

StringBuilder sb = new StringBuilder("hello world");

sb.delete(5, 11);     // "hello"(删除索引5到10)
sb.deleteCharAt(0);   // "ello"(删除索引0的字符)

3.4 替换与反转

StringBuilder sb = new StringBuilder("hello");

sb.replace(0, 2, "HE");  // "HEllo"
sb.reverse();            // "olleH"

3.5 其他方法

StringBuilder sb = new StringBuilder("hello");

sb.length();           // 5(字符数)
sb.capacity();         // 21(容量)
sb.setLength(3);       // "hel"(截断)
sb.charAt(0);          // 'h'
sb.setCharAt(0, 'H');  // "Hel"
sb.toString();         // 转为String

四、使用场景选择

4.1 选择决策树

需要字符串操作?
    ├── 少量操作(<10次)→ String
    └── 大量操作
        ├── 单线程 → StringBuilder
        └── 多线程 → StringBuffer

4.2 具体场景

// 场景1:简单拼接 → String
String name = firstName + " " + lastName;

// 场景2:循环拼接 → StringBuilder
StringBuilder sb = new StringBuilder();
for (String item : list) {
    sb.append(item).append(",");
}

// 场景3:多线程共享 → StringBuffer
StringBuffer buffer = new StringBuffer();
// 多个线程同时操作buffer

// 场景4:SQL拼接 → StringBuilder
StringBuilder sql = new StringBuilder("SELECT * FROM user WHERE 1=1");
if (name != null) {
    sql.append(" AND name = '").append(name).append("'");
}

4.3 预估容量优化

// ❌ 不指定容量,可能多次扩容
StringBuilder sb = new StringBuilder();

// ✅ 预估容量,减少扩容
StringBuilder sb = new StringBuilder(1000);

// 计算预估容量
int estimatedSize = list.size() * averageItemLength;
StringBuilder sb = new StringBuilder(estimatedSize);

五、编译器优化

5.1 字符串拼接优化

// 源码
String s = "a" + "b" + "c";

// JDK 8编译后(StringBuilder)
String s = new StringBuilder().append("a").append("b").append("c").toString();

// JDK 9+编译后(invokedynamic)
// 使用StringConcatFactory,更高效

5.2 循环拼接的问题

// ❌ 编译器无法优化循环
String s = "";
for (int i = 0; i < 100; i++) {
    s += i;  // 每次循环都创建新的StringBuilder
}

// 反编译后
String s = "";
for (int i = 0; i < 100; i++) {
    s = new StringBuilder(s).append(i).toString();  // 每次new
}

// ✅ 手动优化
StringBuilder sb = new StringBuilder();
for (int i = 0; i < 100; i++) {
    sb.append(i);
}
String s = sb.toString();

六、高频面试题

Q1: String、StringBuilder、StringBuffer的区别?

答:

特性 String StringBuilder StringBuffer
可变性 不可变 可变 可变
线程安全
性能 拼接慢 最快 较快
场景 少量操作 单线程拼接 多线程拼接

Q2: StringBuilder的扩容机制是什么?

答:

  • 默认初始容量:16
  • 扩容公式:新容量 = 旧容量 × 2 + 2
  • 如果计算后仍不够,直接使用所需容量
  • 扩容时创建新数组并复制数据

Q3: 为什么StringBuilder比String快?

答:

  • String不可变,每次拼接都创建新对象
  • StringBuilder可变,在原数组上修改
  • 循环拼接时,String创建n个对象,StringBuilder只创建1个

Q4: StringBuffer是如何保证线程安全的?

答:

  • 所有公共方法都用synchronized修饰
  • 同一时刻只有一个线程能执行修改操作
  • 代价是性能损失(约2倍)

Q5: 什么时候用String,什么时候用StringBuilder?

答:

  • String:字符串不需要修改,或只有少量拼接
  • StringBuilder:单线程环境下大量字符串拼接
  • StringBuffer:多线程环境下共享字符串拼接

Q6: StringBuilder初始容量设置多少合适?

答:

  • 预估最终字符串长度
  • 设置为预估长度的1.2~1.5倍
  • 避免设置过大浪费内存,过小频繁扩容

七、最佳实践

✅ 推荐做法

// 1. 循环拼接用StringBuilder
StringBuilder sb = new StringBuilder();
for (String item : list) {
    sb.append(item);
}

// 2. 预估容量
StringBuilder sb = new StringBuilder(list.size() * 20);

// 3. 链式调用
sb.append("name=").append(name).append("&age=").append(age);

// 4. 多线程用StringBuffer
StringBuffer buffer = new StringBuffer();

❌ 避免做法

// 1. 循环中用String拼接
String s = "";
for (int i = 0; i < 1000; i++) {
    s += i;  // 性能差
}

// 2. 单线程用StringBuffer
StringBuffer buffer = new StringBuffer();  // 不必要的同步开销

// 3. 不预估容量
StringBuilder sb = new StringBuilder();  // 可能多次扩容

相关链接


记忆口诀

String不可变,拼接创对象
Builder可变快,单线程首选
Buffer线程安,synchronized保
扩容二倍加二,预估容量好

⬅️ String详解 🏠 00-Java ➡️ BigDecimal