JDK JDK 学习手册8 → 27 · JDK 专用学习站
QUICKREF · STRING · TEXT BLOCKS

String 与文本处理

JDK 21 · 速查主题 03 / 12 · 预计 22 分钟
基础22 分钟
一句话String 是不可变对象,所有「修改」都会产生新对象。JDK 15 的文本块、JDK 11 的 strip / repeat / lines、JDK 21 的 splitWithDelimiters 与带范围 indexOf,让多行文本和子串搜索少写很多样板代码。

学完你要能自己回答

  1. 文本块的缩进规则是什么?stripIndent 与 indent 分别解决什么问题?
  2. split 和 splitWithDelimiters 的区别是什么?limit 参数如何影响结果?
  3. 为什么 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()按行切分为 Stream11适合逐行处理
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 / lines11 正式JDK 8 只有 trim / split。
indent / transform12 正式—
stripIndent / translateEscapes / formatted15 正式文本块相关 API。
splitWithDelimiters / indexOf 范围查找21 正式JDK 21 新增的 String 实例方法。
String Templates21 预览,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 等工具。
学习进度 记录保存在本机浏览器 · 不上传

JDK 学习手册 · JDK / Java 专用学习站 · 纯静态 · 双击 index.html 即可使用

本站专注 JDK 版本演进与 Java 教学,非 Oracle / OpenJDK 官方站点。版本数据来自 OpenJDK 官方 Features 表;课程示例已在对应本机 JDK 实测。构建日期 2026-11-12。