Java 中 String 、StringBuffer 和 StringBuilder
这三个类都是 java.lang 包下的字符串处理类,但它们在设计理念、内部实现和适用场景上存在显著差异。
1. 全面对比表
| 比较维度 | String | StringBuffer | StringBuilder |
|---|---|---|---|
| 可变性 | 不可变(Immutable) 内容一旦创建无法修改 | 可变(Mutable) 支持原地修改 | 可变(Mutable) 支持原地修改 |
| 线程安全 | 天然线程安全(不可变对象) | 线程安全 关键方法(如 append、insert)加了 synchronized 锁 |
非线程安全 无任何同步机制 |
| 性能 | 修改操作最慢(频繁创建新对象) | 中等(有锁开销,但比 String 好) | 最高(无锁,单线程下最优) |
| 内部存储 | 底层是 final char[] value(JDK 7+) JDK 8 前是 char[],不可扩容 |
底层是 char[] value,可动态扩容 |
底层是 char[] value,可动态扩容(实现几乎相同) |
| 容量扩容机制 | 无(固定长度) | 默认容量 16,扩容时新容量 = (旧容量 * 2) + 2 | 同 StringBuffer:默认 16,扩容时新容量 = (旧容量 * 2) + 2 |
| 内存占用 | 每次修改产生新对象 + 新 char[],GC 压力大 | 共享同一 char[],内存高效 | 同 StringBuffer,内存高效 |
| 字符串常量池 | 支持常量池缓存(字面量可复用) | 不支持常量池 | 不支持常量池 |
| 引入版本 | JDK 1.0 | JDK 1.0 | JDK 1.5(为单线程优化而新增) |
| 继承关系 | 继承 AbstractStringBuilder,实现 CharSequence、Serializable、Comparable | 继承 AbstractStringBuilder,实现 Appendable 等 | 继承 AbstractStringBuilder,实现 Appendable 等 |
| 方法同步 | 无需同步 | 大部分修改方法(如 append、delete)都是 synchronized | 无 synchronized |
| 典型使用场景 | 常量字符串、键值存储、配置信息 | 多线程频繁拼接(如日志记录、共享缓冲) | 单线程频繁拼接(如 JSON 构建、循环拼接)------现代项目首选 |
| toString() 实现 | 返回自身(缓存优化 JDK 7+) | 新建 String 对象(调用 Arrays.copyOf) | 同 StringBuffer |
2. 底层实现深入剖析
-
String 的不可变性:
java// JDK 源码简化版 public final class String { private final char[] value; // final 修饰,不可重新赋值 private final int hash; // 缓存 hashCode }任何"修改"操作(如
concat、replace)都会new String()并复制 char[],原对象不变。 -
StringBuffer / StringBuilder 的可变性 :
两者都继承自
AbstractStringBuilder:javaabstract class AbstractStringBuilder { char[] value; // 非 final,可扩容 int count; // 当前长度 }- append 等操作直接操作
value数组。 - 扩容时:
Arrays.copyOf创建更大数组,复制旧内容。
关键区别 :StringBuffer 的公共方法加了
synchronized:java// StringBuffer 示例 public synchronized StringBuffer append(String str) { ... } // StringBuilder 示例 public StringBuilder append(String str) { ... } // 无 synchronized - append 等操作直接操作
3. 性能对比实测(循环 10 万次拼接)
java
public class Test {
public static void main(String[] args) {
int times = 100000;
// String
long start = System.nanoTime();
String s = "";
for (int i = 0; i < times; i++) {
s += i;
}
System.out.println("String 用时: " + (System.nanoTime() - start) / 1e6 + " ms");
// StringBuilder
start = System.nanoTime();
StringBuilder sb = new StringBuilder();
for (int i = 0; i < times; i++) {
sb.append(i);
}
System.out.println("StringBuilder 用时: " + (System.nanoTime() - start) / 1e6 + " ms");
// StringBuffer
start = System.nanoTime();
StringBuffer sbf = new StringBuffer();
for (int i = 0; i < times; i++) {
sbf.append(i);
}
System.out.println("StringBuffer 用时: " + (System.nanoTime() - start) / 1e6 + " ms");
}
}
典型运行结果(JDK 17,普通电脑):
- String:约 3000~5000 ms(极慢,产生海量临时对象)
- StringBuilder:约 10~20 ms
- StringBuffer:约 15~30 ms(略慢于 Builder,因锁开销)
可见,频繁修改时 StringBuilder 性能遥遥领先。
4. 编译器优化小秘密
-
单次 + 操作:JVM 会自动优化为 StringBuilder:
javaString result = "a" + "b" + "c"; // 编译后相当于 new StringBuilder().append("a").append("b").append("c").toString() -
循环中 + 操作:不会跨循环优化,仍建议手动用 StringBuilder。
5. 选择指南(实战总结)
-
内容几乎不修改 → 用 String(最安全、支持常量池)。
-
多线程 + 频繁修改 → 用 StringBuffer(虽慢点但安全)。
-
单线程 + 频繁修改 → 必须用 StringBuilder(性能最佳,99% 场景适用)。
-
已知长度大 → 提前指定容量,避免扩容:
javaStringBuilder sb = new StringBuilder(10000); // 预分配 -
现代替代方案 :复杂场景可考虑
String.join()、String.format()或流式操作,但核心拼接仍推荐 StringBuilder。
6. 常见误区澄清
- 误区:StringBuffer 完全过时了 → 错!在多线程共享同一缓冲时仍有价值。
- 误区:StringBuilder 线程不安全就不能用 → 大多数业务是单线程,安全使用即可。
- 误区:String 完全不能用于拼接 → 小量拼接没问题,大量必须避免。