1067 字
约 3 分钟
6
S04-B08 · 字符串、常量池与编码

适用读者:写过 String s = ""; for(...) s += x; 的人。
前置:S04-B02。
实测环境:OpenJDK 21.0.12.1(2026-11-05)。
覆盖旧文:A-1116(316) A-2409(278) A-1691(78) A-1775(111) A-1769(78) = 861 点。
覆盖扩展:E-S04-009(intern() 与常量池)、E-S04-010(UTF-8 与平台编码)。
上一篇:S04-B07 · 异常处理:分类、最佳实践与常见反模式
下一篇:S04-X01 · 八股速查。


1. 一句话结论

String 不可变,所以能被安全共享、能进常量池、能当 HashMap 的 key;代价是"每次修改都要新建对象"——这就是字符串拼接必须用 StringBuilder 的原因。


2. 实测:常量池、intern 与编译期折叠

String s1 = "hello";
String s2 = new String("hello");
String s3 = s2.intern();

实测输出:

s1==s2: false | s1==s3(intern): true
final 常量拼接 s4==s1: true
非 final 变量拼接 s5==s1: false
写法 是否与 "hello" 同一对象 原因
"hello" 字面量 ✅ 常量池
new String("hello") ❌ 堆上新对象
s2.intern() ✅ 返回池中已有对象
final String C="hel"; C+"lo" ✅ 编译期常量折叠
String v="hel"; v+"lo" ❌ 运行期 StringBuilder 拼接

intern() 的用途(E-S04-009):把字符串放进常量池并复用——适合"大量重复的小字符串"(如状态码、枚举名)。代价是常量池(JDK 7+ 在堆中)会增长,滥用会 OOM。


3. 实测:拼接 20000 次,差 189 倍

String x = "";
for (int i = 0; i < 20000; i++) x += i;      // 每次新建一个 String
StringBuilder sb = new StringBuilder();
for (int i = 0; i < 20000; i++) sb.append(i); // 复用同一缓冲区

实测:

拼接 20000 次: String+= 1477.6 ms | StringBuilder 7.8 ms
方式 复杂度 说明
s += x 循环 O(n²) 每次都复制整个字符串
StringBuilder.append O(n) 内部 char[] 扩容
String.format / +(少量) 可接受 编译器用 invokedynamic 优化(Java 9+)

注意:单次 + 拼接没问题(编译器会优化成 StringBuilder 或 invokedynamic);循环里的 += 才是灾难。


4. 为什么不可变(三条收益)

收益 说明
安全共享 多线程共享无需同步
可做 key hashCode 可缓存,HashMap 依赖稳定性
常量池复用 相同字面量共用一个对象,省内存

代价:每次修改都新建对象 → 用 StringBuilder 解决。


5. 编码:UTF-8 vs 平台默认(E-S04-010)

层 影响因素
源文件编码 javac -encoding(Maven:project.build.sourceEncoding)
运行时字符串 内部是 UTF-16(char 是 16 位)
字节转换 getBytes() 用平台默认编码(不要无参调用)
控制台输出 终端编码(Windows 代码页)

判据:所有 String ↔ byte[] 转换都显式指定编码:

byte[] b = s.getBytes(StandardCharsets.UTF_8);   // ✅
byte[] bad = s.getBytes();                        // ❌ 依赖平台

(编码排障的完整方法论见 S01-J06。)


6. 五个误区

误区 真相
"== 能比较字符串内容" 只比引用;内容用 equals
"intern() 越多越好" 常量池会增长,滥用 OOM
"所有 + 拼接都慢" 单次/常量折叠不慢;循环里才慢
"String 是 UTF-8" 内部 UTF-16;编码只在字节转换时出现
"getBytes() 不带参数没事" 依赖平台默认,跨环境出错

7. 自检

  1. new String("x") == "x" 为什么 false?intern() 之后呢?
  2. 为什么 final 常量拼接能"折叠"?
  3. 循环拼接为什么是 O(n²)?实测差多少倍?
  4. 不可变的三个收益是什么?
  5. 为什么 getBytes() 必须带编码?

8. 下一篇

S04-X01 · 八股速查与跨系列索引

基础层到此结束。下一篇是索引型附录:6 张面试大卡 + 星球总结类卡压成"题 → 一句话答案 → 正文位置",并登记 20 张并发卡的去向(S05)。


本篇属于系列《S04 · Java 语言核心》。旧文来源:A-1116/2409/1691/1775/1769;扩展点 E-S04-009/010。常量池与拼接实测为 OpenJDK 21。

S04-B08 · 字符串、常量池与编码
https://www.clxhxhhr.top/posts/5223/
作者
clxstart
发布于
2024-12-31
许可协议
CC BY-NC-SA 4.0
评论
0 条
还没有评论,先写一条吧。