Java 中的 char、String、StringBuilder 与 StringBuffer 深度详解
(从底层原理到最佳实践,2026 最新版)
这四个类型是 Java 字符串处理的基石,几乎每天都会用到。掌握它们,能让你写出更高效、更安全的代码。
1. char —— 字符的基本单位
char c1 = 'A'; // 正确
char c2 = '中'; // 正确(Unicode)
char c3 = '\u4E2D'; // 十六进制 Unicode 表示“中”
char c4 = 65; // 正确,ASCII 值 65 对应 'A'
核心特性:
- 底层类型:16 位无符号整数(2 字节),范围 0 ~ 65535(
` ~\uFFFF`)。 - 编码:Java 使用 UTF-16 编码(不是 UTF-8!)。
- 注意事项:
- Java 9 之后,
String内部可能使用byte[](Latin-1 优化),但char始终是 2 字节。 - 代理对(Surrogate Pair):一个 emoji(如 😂)可能需要两个
char(高低代理对)。java String emoji = "😂"; System.out.println(emoji.length()); // 输出 2(不是 1!) System.out.println(emoji.codePointCount(0, emoji.length())); // 输出 1
最佳实践:日常很少单独使用 char,更多通过 String.codePointAt() 处理 Unicode 字符。
2. String —— 不可变字符串(Immutable)
2.1 核心特性
- 不可变:一旦创建,内容无法修改。
- 底层存储(JDK 9+):
private final byte[] value;private final byte coder;(LATIN1=0或UTF16=1)- 字符串常量池(String Pool):相同字面量只在堆中保存一份,节省内存。
- 线程安全:天然线程安全(不可变)。
2.2 常见“陷阱”与原理
String s1 = "hello";
String s2 = "hello";
String s3 = new String("hello");
System.out.println(s1 == s2); // true (常量池)
System.out.println(s1 == s3); // false (new 在堆中新建对象)
字符串拼接的性能陷阱:
String result = "";
for (int i = 0; i < 10000; i++) {
result += "a"; // 每次都创建新 String 对象!O(n²) 时间复杂度
}
编译器在编译期会把常量拼接优化为 StringBuilder,但循环中的 += 不会优化。
2.3 常用高效方法(JDK 11+ 推荐)
String.isBlank()、String.lines()、String.strip()String.repeat(int count)String.formatted(Object... args)(替代String.format)
3. StringBuilder —— 单线程高性能可变字符串
核心特性:
- 可变字符序列,底层是
char[](JDK 9+ 是byte[])。 - 非线程安全,速度最快。
- 初始容量 16,超过时自动扩容(
newCapacity = oldCapacity * 2 + 2)。
3.1 推荐使用场景
- 循环拼接字符串
- 单线程构建复杂字符串(JSON、手写 SQL、日志等)
3.2 最佳实践写法
// 推荐:预估容量,避免多次扩容
StringBuilder sb = new StringBuilder(128);
for (int i = 0; i < 10000; i++) {
sb.append("a");
}
String result = sb.toString(); // 最后只调用一次 toString()
链式调用:
String json = new StringBuilder()
.append("{\"name\":\"")
.append(name)
.append("\",\"age\":")
.append(age)
.append("}")
.toString();
4. StringBuffer —— 线程安全的可变字符串
核心特性:
- 与
StringBuilder几乎完全一样。 - 所有公开方法都加了
synchronized。 - 性能比
StringBuilder慢 20%~50%(锁开销)。
4.1 什么时候必须用 StringBuffer?
- 多线程环境下共享同一个可变字符串对象。
- 遗留代码(老项目中大量使用)。
现代推荐:
- 单线程 →
StringBuilder - 多线程 →
StringBuffer或更好的选择:ConcurrentLinkedQueue、CopyOnWriteArrayList+String.join()等。
5. 三者性能对比(实测结论 2026)
| 操作 | String | StringBuilder | StringBuffer |
|---|---|---|---|
| 循环 10 万次拼接 | 最慢(O(n²)) | 最快 | 较慢 |
| 单线程简单拼接 | 慢 | 最快 | 慢 |
| 多线程共享修改 | 安全(不可变) | 不安全 | 安全 |
| 内存占用 | 较高(新对象) | 最低 | 较低 |
基准测试建议:使用 JMH(Java Microbenchmark Harness)验证具体场景。
6. 最佳实践总结(生产级建议)
- 字符串常量 → 直接用双引号
"hello" - 单线程大量拼接 → 优先
StringBuilder,并预估初始容量 - 多线程共享可变字符串 →
StringBuffer - 多线程不共享 → 每个线程用自己的
StringBuilder - 最终结果需要 String → 只在最后调用一次
toString() - JDK 8+ 推荐使用
StringJoiner(更优雅):
String result = Stream.of("a", "b", "c")
.collect(Collectors.joining(","));
- 大字符串处理 → 考虑
StringBuilder+append(char[])或零拷贝技术 - 避免:
- 循环中使用
String += new String("literal")(多此一举)- 不必要的
toString()调用
7. 面试高频问题精讲
- String 为什么不可变?
安全性(类加载、String Pool)、线程安全、作为 HashMap Key 的可靠性。 - StringBuilder 和 StringBuffer 的区别?
线程安全 vs 性能,底层扩容机制完全相同。 - new String(“abc”) 创建了几个对象?
编译期 1 个(常量池) + 运行期 1 个(堆中),共 2 个。 - substring() 方法的性能(JDK 7 vs JDK 8+)?
JDK 7 之前返回原数组子视图(内存泄漏风险),JDK 7+ 复制新数组(安全但耗内存)。
掌握以上内容,你对 Java 字符串的理解就达到了中高级水平。
需要我继续补充以下任意内容,随时说:
String源码深度解析(value、hash、coder)StringBuilder扩容源码 + 位运算详解- JMH 性能测试完整代码
- Spring Boot 中字符串拼接的最佳实践
- 与 Kotlin、Scala 字符串处理的对比
祝你写出又快又安全的 Java 字符串代码!🚀