Java StringBuilder 与 StringBuffer:高性能字符串操作
Java StringBuilder 与 StringBuffer:高性能字符串操作
深入对比 StringBuilder 与 StringBuffer,掌握可变字符串的高效构建、线程安全差异、性能基准与最佳实践。
为什么需要可变字符串?
Java 中 String 是不可变的。每次拼接、修改都会创建新对象,频繁操作导致大量临时对象、GC 压力和性能下降。
String s = "Hello";
s += " World"; // 实际创建新 String 对象,原 "Hello" 成垃圾
为解决此问题,Java 提供 StringBuilder (非线程安全、高性能) 和 StringBuffer (线程安全、同步开销)。
核心区别对比
| 特性 | StringBuilder | StringBuffer |
|---|---|---|
| 线程安全 | 否 (非同步) | 是 (方法同步) |
| 性能 | 高 (无锁开销) | 较低 (同步开销) |
| 引入版本 | JDK 5 | JDK 1.0 |
| 适用场景 | 单线程、高频拼接 | 多线程共享字符串缓冲区 |
| API 兼容性 | 与 StringBuffer 几乎相同 | 遗留 API,部分方法已弃用 |
StringBuilder 核心方法
构造器
StringBuilder()
StringBuilder(int capacity)
StringBuilder(String str)
StringBuilder(CharSequence seq)
常用实例方法
| 方法 | 描述 |
|---|---|
append(...) | 追加各类数据 (重载支持所有基本类型、对象、CharSequence) |
insert(int offset, ...) | 在指定位置插入数据 |
delete(int start, int end) | 删除指定范围字符 |
deleteCharAt(int index) | 删除指定位置字符 |
replace(int start, int end, String str) | 替换指定范围为新字符串 |
reverse() | 反转字符序列 |
charAt(int index) | 获取指定位置字符 |
setCharAt(int index, char ch) | 设置指定位置字符 |
substring(int start) | 返回子序列 (不创建新 String 直到 toString()) |
substring(int start, int end) | 返回指定范围子序列 |
length() | 当前字符数 |
capacity() | 当前容量 (不扩容可容纳字符数) |
ensureCapacity(int minimumCapacity) | 确保最小容量 |
trimToSize() | 缩减容量至当前长度 |
toString() | 转为不可变 String |
StringBuffer 核心方法
StringBuffer 方法与 StringBuilder 签名几乎相同,区别在于所有公共方法均声明 synchronized。
额外遗留方法:
append(Object obj)– 同StringBuildertoStringCache()– 内部缓存优化 (JDK 9+)
性能基准测试
public class Benchmark {
public static void main(String[] args) {
int iterations = 100_000;
// String 拼接
long t1 = System.nanoTime();
String s = "";
for (int i = 0; i < iterations; i++) s += "a";
long t2 = System.nanoTime();
System.out.println("String += : " + (t2 - t1) / 1_000_000 + " ms");
// StringBuilder
long t3 = System.nanoTime();
StringBuilder sb = new StringBuilder();
for (int i = 0; i < iterations; i++) sb.append("a");
long t4 = System.nanoTime();
System.out.println("StringBuilder: " + (t4 - t3) / 1_000_000 + " ms");
// StringBuffer
long t5 = System.nanoTime();
StringBuffer sbf = new StringBuffer();
for (int i = 0; i < iterations; i++) sbf.append("a");
long t6 = System.nanoTime();
System.out.println("StringBuffer: " + (t6 - t5) / 1_000_000 + " ms");
}
}
典型输出 (JDK 17, x86_64):
String += : 4500 ms StringBuilder: 12 ms StringBuffer: 18 ms
结论:StringBuilder 比 String += 快 ~375 倍,比 StringBuffer 快 ~1.5 倍。
容量管理与扩容机制
- 默认初始容量 16 字符 (JDK 5+)。
- 扩容策略:
newCapacity = (oldCapacity + 1) * 2(约 2 倍)。 - 预估最终长度可用
new StringBuilder(estimatedLength)避免多次扩容。
最佳实践
- 单线程首选 StringBuilder:无同步开销,性能最优。
- 多线程共享缓冲区用 StringBuffer:或用
ThreadLocal避免共享。 - 预分配容量:大概知最终长度时用构造器指定
capacity。 - 链式调用:
sb.append("a").append("b").append("c")返回this,可链式调用。 - 避免 toString() 后继续修改:
toString()可能共享底层数组 (JDK 9+ compact strings),后续修改会复制数组。 - JSON/日志构建:高吞吐场景配合
StringBuilder手动拼接优于String.format()。
常见面试题
- Q: StringBuilder 线程安全吗?
- A: 否。多线程环境需自行加锁或用 StringBuffer。
- Q: StringBuilder 与 StringBuffer 性能差异原因?
- A: StringBuffer 所有公共方法 synchronized,锁竞争开销;StringBuilder 无锁。
- Q: 何时用 String 而非 StringBuilder?
- A: 字符串不常修改、作为 Map 键、需哈希码缓存、跨线程共享不可变对象时。
- Q: Java 9+ Compact Strings 对 StringBuilder 有何影响?
- A: 内部改用 byte[] + coder 存储,ASCII 字符仅占 1 字节,内存占用减半,追加性能提升。
推荐阅读:Java String 完全指南 | Java 字符串池与 intern() 机制
java
- 掌握 C# this 关键字:消歧义、构造函数链、传递实例与索引器
- 精通 Java 运算符:类型、语法与实战示例
- Java 注释:类型、用法与最佳实践
- 精通 Java 增强 for 循环:语法、示例与最佳实践
- 精通 Java 构造器:类型、用法与实战示例
- 精通 Java 字符串:创建、方法与最佳实践
- Java StringBuilder 与 StringBuffer:高性能字符串操作
- 深入理解 Java final 关键字:变量、方法与类的不可变性
- 精通 Java 接口:概念、实现与最佳实践
- 精通 Java 封装:数据隐藏实战指南
- 精通 Java 匿名内部类:定义、语法与实战示例
- 精通 Java Try-With-Resources:自动资源管理详解