QUICKREF · STRING · TEXT BLOCKS
String 与文本处理
JDK 21 · 速查主题 03 / 12 · 预计 22 分钟
一句话String 是不可变对象,所有「修改」都会产生新对象。JDK 15 的文本块、JDK 11 的 strip / repeat / lines、JDK 21 的 splitWithDelimiters 与带范围 indexOf,让多行文本和子串搜索少写很多样板代码。
学完你要能自己回答
- 文本块的缩进规则是什么?stripIndent 与 indent 分别解决什么问题?
- split 和 splitWithDelimiters 的区别是什么?limit 参数如何影响结果?
- 为什么 replaceAll 的第一个参数是正则?什么时候必须用 Pattern.quote?
不可变、常量池与比较01
String 内部是 final 字符数据(JDK 9 起用紧凑字符串,Latin-1 用单字节存储)。所有看似修改的方法都返回新 String;循环里用 += 拼接会不断创建对象,应改用 StringBuilder。
String a = "jdk";
String b = "jdk";
String c = new String("jdk");
System.out.println(a == b); // true —— 同一常量池对象
System.out.println(a == c); // false —— new 创建新对象
System.out.println(a.equals(c)); // true —— 内容相等
System.out.println(a == c.intern()); // true
- 字符串字面量与编译期常量拼接会进入常量池;运行期拼接通常产生新对象。
- 比较内容永远用
equals;==只适合判断是否为同一对象。 intern()把字符串放入常量池并返回池中引用,适合大量重复字符串去重,但不要滥用。
文本块:多行字符串02
文本块在 JDK 15 转正,用 """ 开头和结尾,自动处理缩进与转义。它适合 SQL、JSON、HTML 这类多行内容。
String sql = """
SELECT id, name
FROM users
WHERE age >= ?
""";
System.out.println(sql);
// SELECT id, name
// FROM users
// WHERE age >= ?
- incidental whitespace:所有行共同的最小缩进会被移除,内容相对缩进保留。
- 结尾
"""的位置决定最后一行是否包含换行。 - 行尾的
\表示「不换行,直接接下一行」;\s表示一个不会被去掉的行尾空格。 - 文本块内可以直接写双引号,但三个连续双引号需要转义。
String json = """
{
"name": "JDK",
"version": 21
}
""";
System.out.println(json);
String oneLine = """
SELECT *
FROM users \
WHERE id = ?
""";
System.out.println(oneLine);
// SELECT *
// FROM users WHERE id = ?
核心 API 地图03
| 方法 | 作用 | 版本 | 示例 |
|---|---|---|---|
isBlank() | 是否只含空白 | 11 | " ".isBlank() → true |
strip() | 去除首尾 Unicode 空白 | 11 | 比 trim 更完整 |
stripLeading/Trailing() | 单侧去空白 | 11 | — |
indent(n) | 每行缩进 n 格;负数尽量去缩进 | 12 | 会补末尾换行 |
stripIndent() | 去掉共同最小缩进 | 15 | 文本块同款规则 |
translateEscapes() | 把 \\n 等转义变成真实字符 | 15 | 处理外部文本 |
transform(f) | 把字符串交给函数并返回结果 | 12 | 链式处理 |
formatted(args) | String.format 的实例写法 | 15 | "%s-%d".formatted("a", 1) |
repeat(n) | 重复 n 次 | 11 | "ab".repeat(3) |
lines() | 按行切分为 Stream | 11 | 适合逐行处理 |
chars() / codePoints() | 字符 / 码点流 | 8/8 | 注意代理对 |
splitWithDelimiters(regex, limit) | 切分并保留分隔符 | 21 | 新 API |
indexOf(s, begin, end) | 限定区间内查找 | 21 | 越界会抛异常 |
String s = " Hello ";
System.out.println("[" + s.strip() + "]"); // [Hello]
System.out.println("[" + "x".indent(2) + "]"); // [ x\n]
String block = """
line1
line2
""";
System.out.println(block.stripIndent());
String escaped = "a\\nb".translateEscapes();
System.out.println(escaped); // a
// b
System.out.println("ab".repeat(3)); // ababab
System.out.println("jdk".transform(String::toUpperCase)); // JDK
切分、替换与正则04
split 的参数是正则,不是普通字符串。按 .、|、* 等特殊字符切分时必须转义,或用 Pattern.quote。
System.out.println(Arrays.toString("a.b.c".split("\\.")));
System.out.println(Arrays.toString("a|b".split("\\|")));
System.out.println(Arrays.toString("a.b".split(Pattern.quote("."))));
JDK 21 的 splitWithDelimiters 会把分隔符本身保留在结果里,适合重建原文或解析简单表达式。limit 是结果最多切几段;0 表示不限制。
System.out.println(Arrays.toString("a,b,c".splitWithDelimiters(",", 0)));
// [a, ,, b, ,, c]
System.out.println(Arrays.toString("a,b,c".splitWithDelimiters(",", 2)));
// [a, ,, b,c]
System.out.println(Arrays.toString("a,b,".splitWithDelimiters(",", 0)));
// [a, ,, b, ,] —— 末尾空段被保留
普通 split 默认会丢弃末尾空字符串,需要保留时传负 limit。replaceAll / matches 也按正则解释;只替换字面量时用 replace。
System.out.println(Arrays.toString("a,b,".split(","))); // [a, b]
System.out.println(Arrays.toString("a,b,".split(",", -1))); // [a, b, ]
System.out.println("a.b.c".replace(".", "-")); // a-b-c —— 字面量
System.out.println("a.b.c".replaceAll(".", "-")); // ------ —— 正则会匹配任意字符
带范围的 indexOf 在 JDK 21 新增:只在 [beginIndex, endIndex) 内查找,找到返回绝对下标,找不到返回 -1。可以避免先 substring 再查找的额外对象。
String s = "abcabc";
System.out.println(s.indexOf("bc", 0, 3)); // 1
System.out.println(s.indexOf("bc", 0, 1)); // -1
System.out.println(s.indexOf("bc", 2, 6)); // 4
拼接、格式化与性能05
少量拼接直接用 + 即可,编译器会生成 invokedynamic 字符串拼接;循环内拼接用 StringBuilder;分隔符拼接用 String.join 或 Collectors.joining。
String joined = String.join(", ", List.of("a", "b", "c"));
// a, b, c
String built = new StringBuilder()
.append("JDK ")
.append(21)
.append(" LTS")
.toString();
StringBuilder sb = new StringBuilder("ab");
sb.repeat("-", 3); // JDK 21
System.out.println(sb); // ab---
String.format / formatted 便于可读的格式化,但性能低于直接拼接,且受默认 Locale 影响。需要固定 Locale 时显式传入。
String msg = "Hello %s, you are %d".formatted("JDK", 21);
String price = String.format(Locale.US, "%.2f", 12.345);
System.out.println(msg); // Hello JDK, you are 21
System.out.println(price); // 12.35
不要在循环内用
+= 拼接大量文本;也不要在日志里无脑用字符串拼接,SLF4J 的 {} 占位符可以避免不必要的 toString。Unicode 与常见坑06
length()返回的是 UTF-16 code unit 数量,不是「用户看到的字符数」。emoji 可能占两个 code unit。- 需要按码点处理时用
codePoints(),需要按字素簇处理时用BreakIterator或 ICU。 char是 16 位,无法单独表示所有 Unicode 码点。- 比较忽略大小写用
equalsIgnoreCase或Collator(后者适合自然语言排序)。
String emoji = "😀";
System.out.println(emoji.length()); // 2
System.out.println(emoji.codePointCount(0, emoji.length())); // 1
String upper = "straße".toUpperCase();
System.out.println(upper); // STRASSE(取决于 Locale)
// 字符串常量拼接在编译期完成
String s1 = "a" + "b"; // 常量池中的 "ab"
String s2 = "ab";
System.out.println(s1 == s2); // true
// 运行期拼接产生新对象
String x = "a";
String s3 = x + "b";
System.out.println(s3 == s2); // false
不要用
String.format 处理 SQL 或 HTML 拼接,容易引入注入风险。SQL 用 PreparedStatement,HTML 用模板引擎的转义机制。版本边界BOUNDARY
这张表比 API 本身更值得记。写代码时先确认你的运行时版本。
| 特性 / API | 状态 | 说明 |
|---|---|---|
| 文本块 | 15 正式 | 13 预览,14 二次预览。 |
| strip / repeat / lines | 11 正式 | JDK 8 只有 trim / split。 |
| indent / transform | 12 正式 | — |
| stripIndent / translateEscapes / formatted | 15 正式 | 文本块相关 API。 |
| splitWithDelimiters / indexOf 范围查找 | 21 正式 | JDK 21 新增的 String 实例方法。 |
| String Templates | 21 预览,23 撤回 | 不要在生产代码中使用。 |
费曼自检SELF CHECK
先合上页面,用自己的话回答,再展开对照。能讲出因果和反例,才算真正掌握。
01. 文本块和普通字符串相比,缩进是如何处理的?
文本块会计算所有非空行的共同最小缩进(incidental whitespace),并从每行移除;相对缩进保留。stripIndent() 使用同一规则。indent(n) 则是显式增加或减少缩进,并且会在末尾补换行。
02. splitWithDelimiters 与 split 的核心区别是什么?
splitWithDelimiters 会把匹配到的分隔符作为独立元素保留在结果数组中,便于重建原文;split 只返回被切分的片段。两者都接受 limit,limit=2 时最后一段包含剩余未切分内容。
03. 为什么 replaceAll(".", "-") 会把整个字符串都替换掉?
replaceAll 的第一个参数是正则表达式,
. 在正则中匹配任意字符,因此每个字符都被替换。要替换字面量的点,使用 replace(".", "-") 或 replaceAll(Pattern.quote("."), "-")。04. String.length() 能表示用户看到的字符数吗?
不能。length() 返回 UTF-16 code unit 数;一个 emoji 等增补平面字符占两个 code unit。按码点计数要用 codePointCount,按字素簇计数需要 BreakIterator 等工具。
学习进度
记录保存在本机浏览器 · 不上传