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(); // 可能多次扩容
相关链接
- 01-String详解 - String的不可变性和常量池
- 03-BigDecimal - 精确数值计算
记忆口诀
String不可变,拼接创对象
Builder可变快,单线程首选
Buffer线程安,synchronized保
扩容二倍加二,预估容量好
⬅️ String详解 🏠 00-Java ➡️ BigDecimal
💬 评论