前言
这三个类都是 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 的不可变性:
// jdk 源码简化版 public final class string { private final char[] value; // final 修饰,不可重新赋值 private final int hash; // 缓存 hashcode }任何“修改”操作(如
concat、replace)都会new string()并复制 char[],原对象不变。stringbuffer / stringbuilder 的可变性:
两者都继承自abstractstringbuilder:abstract class abstractstringbuilder { char[] value; // 非 final,可扩容 int count; // 当前长度 }- append 等操作直接操作
value数组。 - 扩容时:
arrays.copyof创建更大数组,复制旧内容。
关键区别:stringbuffer 的公共方法加了
synchronized:// stringbuffer 示例 public synchronized stringbuffer append(string str) { ... } // stringbuilder 示例 public stringbuilder append(string str) { ... } // 无 synchronized- append 等操作直接操作
3. 性能对比实测(循环 10 万次拼接)
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:
string result = "a" + "b" + "c"; // 编译后相当于 new stringbuilder().append("a").append("b").append("c").tostring()循环中 + 操作:不会跨循环优化,仍建议手动用 stringbuilder。
5. 选择指南(实战总结)
内容几乎不修改 → 用 string(最安全、支持常量池)。
多线程 + 频繁修改 → 用 stringbuffer(虽慢点但安全)。
单线程 + 频繁修改 → 必须用 stringbuilder(性能最佳,99% 场景适用)。
已知长度大 → 提前指定容量,避免扩容:
stringbuilder sb = new stringbuilder(10000); // 预分配
现代替代方案:复杂场景可考虑
string.join()、string.format()或流式操作,但核心拼接仍推荐 stringbuilder。
6. 常见误区澄清
- 误区:stringbuffer 完全过时了 → 错!在多线程共享同一缓冲时仍有价值。
- 误区:stringbuilder 线程不安全就不能用 → 大多数业务是单线程,安全使用即可。
- 误区:string 完全不能用于拼接 → 小量拼接没问题,大量必须避免。
总结
到此这篇关于java中string、stringbuffer和stringbuilder底层实现的文章就介绍到这了,更多相关java中string、stringbuffer和stringbuilder内容请搜索代码网以前的文章或继续浏览下面的相关文章希望大家以后多多支持代码网!
发表评论