String 与文本处理
学习目标
- 理解
String不可变性、字符串池和内容比较规则。 - 使用查找、截取、替换、分割、格式化和
StringBuilder完成常见文本处理。 - 区分 UTF-8 字节、UTF-16 码元、Unicode 码点与用户可见字符。
- 能根据“字面匹配还是正则匹配”选择当前页面或正则表达式专题。
核心知识点
专业术语
- 不可变对象(immutable object):
String创建后内容不再改变,拼接或替换会返回新的字符串对象。 - 字符串池(string pool):运行时保存部分字符串字面量的共享区域,不能作为
==内容比较的依据。 - 字符集(charset):把字符与字节编码互相转换的规则;跨边界文本应显式指定
UTF-8等字符集。 - UTF-16 code unit 与 Unicode code point:Java 的
char是 16 位码元,一个码点可能需要两个码元。 - 字面匹配与正则匹配:
replace等 API 按字面处理,replaceAll、split的参数按正则解释。
白话解释与边界
字符串像一张不可擦写的纸:trim、replace 或 + 不会修改原对象,而是产生新值。少量拼接没有问题;循环中大量拼接应使用 StringBuilder,避免反复创建中间字符串。文本离开 Java 内存时才是字节,读写必须指定字符集,不能把当前系统默认编码当成稳定协议。
String.length() 返回 UTF-16 码元数量,不一定等于 Unicode 码点数量,更不等于用户看到的字素簇数量。正则适合字段级清洗;嵌套语法或需要精确错误位置的格式应使用专门解析器。JSON 转换已拆到JSON 与 Java 对象转换,大数运算已拆到大数与精确计算。
常用用法
String API:快速入口
需要快速完成文本清理与规范化时使用这段最小示例。
// 语义:需要快速完成文本清理与规范化时使用这段最小示例。
// 初始状态:value 初始为 " Java ";normalized 初始为 value.strip().toLowerCase()。
String value = " Java ";
// 初始状态:value 当前为 " Java "。
// 作用:需要快速完成文本清理与规范化时使用这段最小示例。
String normalized = value.strip().toLowerCase();
// 返回:value.strip().toLowerCase() 去掉两端空格并转小写,normalized 为 "java"。
System.out.println(normalized);
// 输出:java按字面查找和替换先看本页;需要正则捕获组时转到正则表达式与文本匹配。
JSONUtil:JSON 转换专题入口
需要把 Java 对象序列化为 JSON 文本时使用 JSONUtil.toJsonStr,完整边界转到 JSON 专题查阅。
import cn.hutool.json.JSONUtil;
import java.util.Map;
String json = JSONUtil.toJsonStr(Map.of("name", "Ann"));
// 初始状态:json 当前为 JSONUtil.toJsonStr(Map.of("name", "Ann"))。
// 返回:JSONUtil.toJsonStr(Map.of("name", "Ann")) 生成 JSON 文本 {"name":"Ann"}。
// 作用:需要把 Java 对象序列化为 JSON 文本时使用 JSONUtil.toJsonStr,完整边界转到 JSON 专题查阅。
System.out.println(json);
// 输出:{"name":"Ann"}JSON 的完整依赖、Bean、数组和泛型边界见JSON 与 Java 对象转换。
字符串字面量:创建字符串
已知文本内容时优先使用字符串字面量;new String("Java") 不会带来内容收益。
// 语义:已知文本内容时优先使用字符串字面量。
// 初始状态:literal 初始为 "Java";same 初始为 "Java"。
String literal = "Java";
// 关键变化:literal 接收表达式 "Java" 的计算结果。
// 初始状态:literal 的初始值为 "Java"。
String same = "Java";
boolean sameContent = literal.equals(same);
System.out.println(literal);
// 输出:Java
System.out.println(sameContent);
// 输出:truenew String:按字符集解码字节
已有外部字节时用带字符集的构造器解码,并显式指定字符集。
// 语义:已有外部字节时用带字符集的构造器解码,并显式指定字符集。
// 初始状态:bytes 初始为 {74, 97, 118, 97};decoded 初始为 new String(bytes, StandardCharsets.UTF_8)。
import java.nio.charset.StandardCharsets;
byte[] bytes = {74, 97, 118, 97};
// 初始状态:bytes 当前为 {74, 97, 118, 97}。
String decoded = new String(bytes, StandardCharsets.UTF_8);
// 返回:new String(bytes, StandardCharsets.UTF_8) 将 {74,97,118,97} 按 UTF-8 解码为 "Java"。
// 作用:已有外部字节时用带字符集的构造器解码,并显式指定字符集。
System.out.println(decoded);
// 输出:Java
System.out.println(decoded.length());
// 输出:4isEmpty:判断空字符串
isEmpty() 只判断长度是否为零,不会把空格或换行视为空字符串。
String empty = "";
// 初始状态:empty 当前为 ""。
String spaces = " \n";
// 初始状态:spaces 当前为 " \n"。
System.out.println(empty.isEmpty());
// 输出:true
// 作用:isEmpty() 只判断长度是否为零,不会把空格或换行视为空字符串。
System.out.println(spaces.isEmpty());
// 输出:falseisBlank:判断空白字符串
isBlank() 判断空串或仅包含 Unicode 空白的字符串;调用前仍需单独处理 null。
String spaces = " \n";
// 初始状态:spaces 当前为 " \n"。
String word = " Java ";
// 初始状态:word 当前为 " Java "。
boolean blank = spaces.isBlank();
// 作用:isBlank() 判断空串或仅包含 Unicode 空白的字符串。
// 关键变化:spaces.isBlank() 检查字符串 " \n" 全为空白,blank 变为 true。
boolean wordBlank = word.isBlank();
// 返回:wordBlank 接收 word.isBlank() 的返回值。
// 关键变化:word.isBlank() 检查 " Java " 含非空白字符,wordBlank 变为 false。
System.out.println(blank);
// 输出:true
System.out.println(wordBlank);
// 输出:falsecharAt:访问 UTF-16 码元
charAt 按 UTF-16 索引返回一个 char 码元,可能只取得补充平面字符的一半。
String text = "A🙂B";
// 初始状态:text 当前为 "A🙂B"。
char first = text.charAt(0);
// 初始状态:first 当前为 text.charAt(0)。
// 返回:text.charAt(0) 从 "A🙂B" 读取索引 0 的码元 'A'。
// 作用:charAt 按 UTF-16 索引返回一个 char 码元,可能只取得补充平面字符的一半。
// 关键变化:text.charAt(0) 从 "A🙂B" 读取索引 0 的码元,first 变为 'A'。
char surrogate = text.charAt(1);
// 返回:surrogate 接收 text.charAt(1) 的返回值。
// 关键变化:text.charAt(1) 读取 🙂 的首个 UTF-16 码元,surrogate 变为高代理项。
int surrogateValue = surrogate;
System.out.println(first);
// 输出:A
System.out.println(surrogateValue);
// 输出:55357codePointAt:访问 Unicode 码点
codePointAt 从指定 UTF-16 索引读取完整码点,适合处理表情等补充平面字符。
String text = "A🙂B";
// 初始状态:text 当前为 "A🙂B"。
int point = text.codePointAt(1);
// 初始状态:point 当前为 text.codePointAt(1)。
// 返回:text.codePointAt(1) 读取 "🙂" 的码点 128578,point 转十六进制为 1f642。
// 作用:codePointAt 从指定 UTF-16 索引读取完整码点,适合处理表情等补充平面字符。
String hex = Integer.toHexString(point);
String restored = new String(Character.toChars(point));
System.out.println(hex);
// 输出:1f642
System.out.println(restored);
// 输出:🙂codePoints:遍历 Unicode 码点
codePoints() 生成码点流,避免按 char 遍历时拆开补充平面字符。
String text = "A🙂B";
// 初始状态:text 当前为 "A🙂B"。
long count = text.codePoints().count();
// 初始状态:count 当前为 text.codePoints().count()。
// 返回:text.codePoints().count() 统计 "A🙂B" 的 3 个 Unicode 码点。
// 作用:codePoints() 生成码点流,避免按 char 遍历时拆开补充平面字符。
// 关键变化:text.codePoints().count() 统计 "A🙂B" 的码点数量,count 变为 3。
String joined = text.codePoints()
.mapToObj(cp -> new String(Character.toChars(cp)))
.reduce("", String::concat);
// 返回:codePoints().mapToObj(...).reduce("", String::concat) 重新拼接为 "A🙂B"。
// 关键变化:text.codePoints().mapToObj(...).reduce("", String::concat) 重新拼接码点,joined 变为 "A🙂B"。
System.out.println(count);
// 输出:3
System.out.println(joined);
// 输出:A🙂Bequals:比较字符串内容
equals 区分大小写地比较内容,不能用 == 代替;可能为 null 时可让常量调用。
String input = new String("Java");
// 初始状态:input 当前为 new String("Java")。
boolean sameContent = "Java".equals(input);
// 初始状态:sameContent 当前为 "Java".equals(input)。
// 返回:"Java".equals(input) 比较相同文本,sameContent 为 true。
// 作用:equals 区分大小写地比较内容,不能用 == 代替。
boolean sameObject = "Java" == input;
System.out.println(sameContent);
// 输出:true
System.out.println(sameObject);
// 输出:falseequalsIgnoreCase:忽略大小写比较内容
业务规则明确允许忽略大小写时使用 equalsIgnoreCase,它不负责完整的语言区域规范化。
String input = "JAVA";
// 初始状态:input 当前为 "JAVA"。
boolean exact = "java".equals(input);
// 初始状态:exact 当前为 "java".equals(input)。
boolean relaxed = "java".equalsIgnoreCase(input);
// 返回:"java".equalsIgnoreCase("JAVA") 忽略大小写后相等,relaxed 为 true。
// 作用:业务规则明确允许忽略大小写时使用 equalsIgnoreCase,它不负责完整的语言区域规范化。
System.out.println(exact);
// 输出:false
System.out.println(relaxed);
// 输出:trueindexOf:查找首次出现位置
indexOf 按字面文本返回首次匹配的 UTF-16 索引,找不到时返回 -1。
String path = "/api/users/api";
// 初始状态:path 当前为 "/api/users/api"。
int first = path.indexOf("/api");
// 初始状态:first 当前为 path.indexOf("/api")。
// 返回:path.indexOf("/api") 在 "/api/users/api" 中找到首次位置 0。
// 作用:indexOf 按字面文本返回首次匹配的 UTF-16 索引,找不到时返回 -1。
// 关键变化:path.indexOf("/api") 在 path="/api/users/api" 中找到索引 0,first 变为 0。
int missing = path.indexOf("orders");
// 返回:missing 接收 path.indexOf("orders") 的返回值。
// 关键变化:path.indexOf("orders") 未找到匹配,missing 变为 -1。
System.out.println(first);
// 输出:0
System.out.println(missing);
// 输出:-1lastIndexOf:查找最后出现位置
lastIndexOf 按字面文本返回最后一次匹配的位置,适合定位最后一个分隔符。
String path = "/api/users/api";
// 初始状态:path 当前为 "/api/users/api"。
int lastApi = path.lastIndexOf("/api");
// 初始状态:lastApi 当前为 path.lastIndexOf("/api")。
// 返回:path.lastIndexOf("/api") 返回最后一次出现的索引 10。
// 作用:lastIndexOf 按字面文本返回最后一次匹配的位置,适合定位最后一个分隔符。
// 关键变化:path.lastIndexOf("/api") 在 path="/api/users/api" 中找到最后一个起点 10,lastApi 变为 10。
int lastSlash = path.lastIndexOf('/');
// 返回:lastSlash 接收 path.lastIndexOf('/') 的返回值。
// 关键变化:path.lastIndexOf('/') 在 path="/api/users/api" 中找到最后一个斜杠索引 15,lastSlash 变为 15。
System.out.println(lastApi);
// 输出:10
System.out.println(lastSlash);
// 输出:15contains:判断是否包含文本
只关心有无字面子串时使用 contains,它不把参数解释为正则表达式。
String path = "/api/users";
// 初始状态:path 当前为 "/api/users"。
boolean hasUsers = path.contains("users");
// 初始状态:hasUsers 当前为 path.contains("users")。
// 返回:path.contains("users") 在 "/api/users" 中找到子串,hasUsers 为 true。
// 作用:只关心有无字面子串时使用 contains,它不把参数解释为正则表达式。
// 关键变化:path.contains("users") 检查 path="/api/users" 后让 hasUsers 变为 true。
boolean hasPattern = path.contains(".*");
// 返回:hasPattern 接收 path.contains(".*") 的返回值。
// 关键变化:path.contains(".*") 按字面检查 path="/api/users",未找到该文本,hasPattern 变为 false。
System.out.println(hasUsers);
// 输出:true
System.out.println(hasPattern);
// 输出:falseString.join:按分隔符拼接
需要用同一分隔符连接多个元素时使用 String.join。
String id = "42";
// 初始状态:id 当前为 "42"。
String path = String.join("/", "api", "users", id);
// 初始状态:path 当前为 String.join("/", "api", "users", id)。
// 返回:String.join("/", "api", "users", "42") 拼出 "api/users/42"。
// 作用:需要用同一分隔符连接多个元素时使用 String.join。
// 关键变化:String.join("/", "api", "users", id) 使用 id="42" 拼接,path 变为 "api/users/42"。
String csv = String.join(",", "Java", "SQL");
// 返回:csv 接收 String.join(",", "Java", "SQL") 的返回值。
// 关键变化:String.join(",", "Java", "SQL") 用逗号拼接两个文本,csv 变为 "Java,SQL"。
System.out.println(path);
// 输出:api/users/42
System.out.println(csv);
// 输出:Java,SQLconcat:拼接一个字符串
concat 把一个非 null 字符串追加到末尾并返回新字符串,循环拼接仍应使用 StringBuilder。
String prefix = "Java";
// 初始状态:prefix 当前为 "Java"。
String result = prefix.concat("速查");
// 初始状态:result 当前为 prefix.concat("速查")。
// 返回:"Java".concat("速查") 创建新字符串 "Java速查",prefix 仍为 "Java"。
// 作用:concat 把一个非 null 字符串追加到末尾并返回新字符串,循环拼接仍应使用 StringBuilder。
boolean unchanged = prefix.equals("Java");
System.out.println(result);
// 输出:Java速查
System.out.println(unchanged);
// 输出:trueformatted:用模板实例格式化
已有格式模板时使用实例方法 formatted 代入参数,格式说明符必须和参数类型匹配。
int id = 7;
// 初始状态:id 当前为 7。
String name = "Ann";
// 初始状态:name 当前为 "Ann"。
String template = "id=%d, name=%s";
String line = template.formatted(id, name);
// 返回:template.formatted(7, "Ann") 按模板生成 "id=7, name=Ann"。
// 作用:已有格式模板时使用实例方法 formatted 代入参数,格式说明符必须和参数类型匹配。
System.out.println(line);
// 输出:id=7, name=Ann
System.out.println(template);
// 输出:id=%d, name=%sString.format:静态格式化字符串
动态选择模板或语言区域时使用 String.format,稳定协议文本宜显式指定 Locale。
import java.util.Locale;
double rate = 0.25;
// 初始状态:rate 当前为 0.25。
String line = String.format(Locale.ROOT, "rate=%.2f", rate);
// 初始状态:line 当前为 String.format(Locale.ROOT, "rate=%.2f", rate)。
// 返回:String.format(Locale.ROOT, "rate=%.2f", 0.25) 生成 "rate=0.25"。
// 作用:动态选择模板或语言区域时使用 String.format,稳定协议文本宜显式指定 Locale。
System.out.println(line);
// 输出:rate=0.25
System.out.println(rate);
// 输出:0.25toUpperCase:转换为大写
协议字段或键名转大写时指定 Locale.ROOT,方法会返回新字符串。
import java.util.Locale;
String text = "Java Api";
// 初始状态:text 当前为 "Java Api"。
String upper = text.toUpperCase(Locale.ROOT);
// 初始状态:upper 当前为 text.toUpperCase(Locale.ROOT)。
// 返回:text.toUpperCase(Locale.ROOT) 将 "Java Api" 转为 "JAVA API"。
// 作用:协议字段或键名转大写时指定 Locale.ROOT,方法会返回新字符串。
System.out.println(upper);
// 输出:JAVA API
System.out.println(text);
// 输出:Java ApitoLowerCase:转换为小写
协议字段或键名转小写时指定 Locale.ROOT,避免依赖默认语言区域。
import java.util.Locale;
String text = "Java API";
// 初始状态:text 当前为 "Java API"。
String lower = text.toLowerCase(Locale.ROOT);
// 初始状态:lower 当前为 text.toLowerCase(Locale.ROOT)。
// 返回:text.toLowerCase(Locale.ROOT) 将 "Java API" 转为 "java api"。
// 作用:协议字段或键名转小写时指定 Locale.ROOT,避免依赖默认语言区域。
System.out.println(lower);
// 输出:java api
System.out.println(text);
// 输出:Java APItrim:去除旧式两端空白
兼容旧代码时用 trim 去除两端不大于 U+0020 的字符,新代码处理 Unicode 空白通常选 strip。
String input = " Java ";
// 初始状态:input 当前为 " Java "。
String result = input.trim();
// 初始状态:result 当前为 input.trim()。
// 返回:input.trim() 删除两端 ASCII 控制空白,result 为 "Java"。
// 作用:兼容旧代码时用 trim 去除两端不大于 U+0020 的字符,新代码处理 Unicode 空白通常选 strip。
int length = result.length();
System.out.println(result);
// 输出:Java
System.out.println(length);
// 输出:4strip:去除 Unicode 两端空白
使用 strip 按 Unicode 空白规则清理字符串两端,原字符串不会改变。
String input = "\u2003Java\u2003";
// 初始状态:input 当前为 "\u2003Java\u2003"。
String result = input.strip();
// 初始状态:result 当前为 input.strip()。
// 返回:input.strip() 按 Unicode 空白规则去除两端空格,result 为 "Java",input 不变。
// 作用:使用 strip 按 Unicode 空白规则清理字符串两端,原字符串不会改变。
boolean originalChanged = input.equals(result);
System.out.println(result);
// 输出:Java
System.out.println(originalChanged);
// 输出:falsetoCharArray:转换为字符数组
需要可修改的 UTF-16 码元副本时用 toCharArray,它不保证每项都是完整 Unicode 字符。
String text = "Java";
// 初始状态:text 当前为 "Java"。
char[] chars = text.toCharArray();
// 初始状态:chars 当前为 text.toCharArray()。
// 返回:text.toCharArray() 复制 "Java" 的码元,chars 初始为 ['J','a','v','a']。
// 作用:需要可修改的 UTF-16 码元副本时用 toCharArray,它不保证每项都是完整 Unicode 字符。
chars[0] = 'L';
String changed = new String(chars);
System.out.println(changed);
// 输出:Lava
System.out.println(text);
// 输出:JavagetBytes:按字符集编码为字节
跨文件或网络边界时用 getBytes 并显式指定字符集,避免平台默认编码差异。
import java.nio.charset.StandardCharsets;
String text = "Java";
// 初始状态:text 当前为 "Java"。
byte[] bytes = text.getBytes(StandardCharsets.UTF_8);
// 初始状态:bytes 当前为 text.getBytes(StandardCharsets.UTF_8)。
// 返回:text.getBytes(StandardCharsets.UTF_8) 将 "Java" 编码为 4 个 UTF-8 字节。
// 作用:跨文件或网络边界时用 getBytes 并显式指定字符集,避免平台默认编码差异。
String decoded = new String(bytes, StandardCharsets.UTF_8);
System.out.println(bytes.length);
// 输出:4
System.out.println(decoded);
// 输出:JavaStringBuilder.append:追加内容
循环构建文本时用 append 追加内容,最后调用 toString 取得不可变字符串。
StringBuilder builder = new StringBuilder();
// 初始状态:builder 当前为 new StringBuilder()。
builder.append("Java");
// 关键变化:builder.append("Java") 把文本追加到空构建器,builder 变为 "Java"。
// 作用:循环构建文本时用 append 追加内容,最后调用 toString 取得不可变字符串。
builder.append(' ');
// 关键变化:builder.append(' ') 在末尾追加一个空格,builder 变为 "Java API "。
builder.append(21);
// 关键变化:builder.append(21) 在末尾追加数字 21,builder 变为 "Java API 21"。
String result = builder.toString();
// 初始状态:result 当前为 builder.toString()。
System.out.println(result);
// 输出:Java 21StringBuilder.insert:在指定位置插入内容
需要在已有可变文本中间插入内容时使用 insert,索引按 UTF-16 码元计算。
StringBuilder builder = new StringBuilder("Java速查");
// 初始状态:builder 当前为 new StringBuilder("Java速查")。
builder.insert(0, '《');
// 关键变化:builder.insert(0, '《') 在开头插入左书名号,builder 变为 "《Java速查"。
// 作用:需要在已有可变文本中间插入内容时使用 insert,索引按 UTF-16 码元计算。
builder.insert(builder.length(), '》');
// 关键变化:builder.insert(builder.length(), '》') 在末尾索引处插入 "》",builder 变为 "Java API 21》"。
String result = builder.toString();
// 初始状态:result 当前为 builder.toString()。
System.out.println(result);
// 输出:《Java速查》
System.out.println(builder.length());
// 输出:8StringBuilder.delete:删除指定区间
使用 delete 删除左闭右开的码元区间,调用会直接修改当前构建器。
StringBuilder builder = new StringBuilder("Java--速查");
// 初始状态:builder 当前为 new StringBuilder("Java--速查")。
int start = builder.indexOf("--");
// 初始状态:start 当前为 builder.indexOf("--")。
builder.delete(start, start + 2);
// 关键变化:builder.delete(start, start + 2) 删除 "--",builder 变为 "Java速查"。
// 作用:使用 delete 删除左闭右开的码元区间,调用会直接修改当前构建器。
String result = builder.toString();
System.out.println(result);
// 输出:Java速查
System.out.println(builder.length());
// 输出:6length():获取 UTF-16 码元长度
length() 返回 UTF-16 码元数量,不等同于 Unicode 码点数或用户看到的字符数。
String text = "Java🙂";
// 关键变化:text 接收表达式 "Java🙂" 的计算结果。
// 初始状态:text 当前为 "Java🙂"。
int units = text.length();
// 初始状态:units 当前为 text.length()。
// 返回:text.length() 统计 "Java🙂" 的 UTF-16 码元数 6。
// 作用:length() 返回 UTF-16 码元数量,不等同于 Unicode 码点数或用户看到的字符数。
int points = text.codePointCount(0, units);
boolean differs = units != points;
System.out.println(units);
// 输出:6
System.out.println(points);
// 输出:5
System.out.println(differs);
// 输出:truecompareTo:区分大小写地按字典序比较
compareTo 返回负数、零或正数,适合排序,不应假定非零结果一定是 -1 或 1。
String left = "Java";
// 初始状态:left 当前为 "Java"。
String right = "java";
// 初始状态:right 当前为 "java"。
int order = left.compareTo(right);
// 返回:"Java".compareTo("java") 返回负数,order < 0 为 true。
// 作用:compareTo 返回负数、零或正数,适合排序,不应假定非零结果一定是 -1 或 1。
boolean before = order < 0;
System.out.println(before);
// 输出:true
System.out.println(order == 0);
// 输出:falsecompareToIgnoreCase:忽略大小写地按字典序比较
排序规则允许忽略大小写时使用 compareToIgnoreCase,复杂本地化排序应改用 Collator。
String left = "Java";
// 初始状态:left 当前为 "Java"。
String right = "java";
// 初始状态:right 当前为 "java"。
int order = left.compareToIgnoreCase(right);
// 返回:"Java".compareToIgnoreCase("java") 返回 0,表示忽略大小写后相等。
// 作用:排序规则允许忽略大小写时使用 compareToIgnoreCase,复杂本地化排序应改用 Collator。
// 关键变化:left.compareToIgnoreCase(right) 比较 left="Java" 与 right="java",order 变为 0。
boolean sameOrder = order == 0;
System.out.println(order);
// 输出:0
System.out.println(sameOrder);
// 输出:truestartsWith:判断字符串前缀
startsWith 按字面内容判断开头,可用偏移重载从指定位置开始匹配。
String path = "backup/data.json";
// 初始状态:path 当前为 "backup/data.json"。
// 初始状态:backup 当前为 path.startsWith("backup/")。
boolean backup = path.startsWith("backup/");
// 返回:path.startsWith("backup/") 检查前缀并得到 true。
// 作用:startsWith 按字面内容判断开头,可用偏移重载从指定位置开始匹配。
// 关键变化:path.startsWith("backup/") 检查 path="backup/data.json" 的前缀,backup 变为 true。
boolean dataAtSeven = path.startsWith("data", 7);
// 返回:dataAtSeven 接收 path.startsWith("data", 7) 的返回值。
// 关键变化:path.startsWith("data", 7) 从索引 7 检查 path,dataAtSeven 变为 true。
System.out.println("backup=" + backup);
// 输出:backup=true
System.out.println("data=" + dataAtSeven);
// 输出:data=trueendsWith:判断字符串后缀
endsWith 按字面内容判断结尾,适合已完成大小写和路径规范化后的扩展名检查。
String fileName = "data.json";
// 初始状态:fileName 当前为 "data.json"。
// 初始状态:json 当前为 fileName.endsWith(".json")。
boolean json = fileName.endsWith(".json");
// 返回:fileName.endsWith(".json") 检查 "data.json" 后缀并得到 true。
// 作用:endsWith 按字面内容判断结尾,适合已完成大小写和路径规范化后的扩展名检查。
// 关键变化:fileName.endsWith(".json") 检查 fileName="data.json" 的后缀,json 变为 true。
boolean xml = fileName.endsWith(".xml");
// 返回:xml 接收 fileName.endsWith(".xml") 的返回值。
// 关键变化:fileName.endsWith(".xml") 检查 fileName="data.json",未匹配时 xml 变为 false。
System.out.println(json);
// 输出:true
System.out.println(xml);
// 输出:falseString.substring:截取字符串区间
substring 按左闭右开区间截取文本,索引按 UTF-16 码元计算。
String text = "Java速查";
// 初始状态:text 当前为 "Java速查"。
String prefix = text.substring(0, 4);
// 初始状态:prefix 当前为 text.substring(0, 4)。
// 返回:text.substring(0, 4) 取左闭右开区间,prefix 为 "Java"。
// 作用:substring 按左闭右开区间截取文本,索引按 UTF-16 码元计算。
// 关键变化:text.substring(0, 4) 截取 text="Java速查" 的索引 0..3,prefix 变为 "Java"。
String suffix = text.substring(4);
// 返回:suffix 接收 text.substring(4) 的返回值。
// 关键变化:text.substring(4) 截取 text="Java速查" 的后缀,suffix 变为 "速查"。
System.out.println(prefix);
// 输出:Java
System.out.println(suffix);
// 输出:速查replace:按字面替换
replace 按字面匹配字符或字符序列并返回新字符串,不把参数解释为正则。
String text = "JAVA,Java";
// 初始状态:text 当前为 "JAVA,Java"。
String punctuation = text.replace(',', ',');
// 初始状态:punctuation 当前为 text.replace(',', ',')。
// 返回:text.replace(',', ',') 把全角逗号换成半角逗号,punctuation 为 "JAVA,Java"。
// 作用:replace 按字面匹配字符或字符序列并返回新字符串,不把参数解释为正则。
// 关键变化:text.replace(',', ',') 把 text="JAVA,Java" 的全角逗号替换,punctuation 变为 "JAVA,Java"。
String normalized = punctuation.replace("JAVA", "Java");
// 返回:normalized 接收 punctuation.replace("JAVA", "Java") 的返回值。
// 关键变化:punctuation.replace("JAVA", "Java") 替换首段文本,normalized 变为 "Java,Java"。
System.out.println(normalized);
// 输出:Java,Java
System.out.println(text);
// 输出:JAVA,Javasplit:按正则分割文本
split 的参数是正则表达式;传负 limit 可保留末尾空字段。
String csv = "Java,,SQL,";
// 初始状态:csv 当前为 "Java,,SQL,"。
String[] fields = csv.split(",", -1);
// 初始状态:fields 当前为 csv.split(",", -1)。
// 返回:csv.split(",", -1) 保留末尾空字段,fields 为 ["Java", "", "SQL", ""]。
// 作用:split 的参数是正则表达式。
int count = fields.length;
System.out.println(java.util.Arrays.toString(fields));
// 输出:[Java, , SQL, ]
System.out.println(count);
// 输出:4repeat:重复字符串
repeat 返回重复指定次数的新字符串,次数为零时返回空串,负数会抛异常。
int level = 3;
// 初始状态:level 当前为 3。
String unit = " ";
// 初始状态:unit 当前为 " "。
String indent = unit.repeat(level);
// 返回:unit.repeat(3) 生成 6 个空格,indent.length() 为 6。
// 作用:repeat 返回重复指定次数的新字符串,次数为零时返回空串,负数会抛异常。
int length = indent.length();
System.out.println(length);
// 输出:6
System.out.println(indent.isEmpty());
// 输出:falseString.valueOf:把值转换为字符串
String.valueOf 可统一转换基本值或对象,传入 null 对象时返回文本 "null"。
Object maybeNull = null;
// 初始状态:maybeNull 当前为 null。
String label = String.valueOf(maybeNull);
// 初始状态:label 当前为 String.valueOf(maybeNull)。
// 返回:String.valueOf(null) 返回文本 "null",label 为 "null"。
// 作用:String.valueOf 可统一转换基本值或对象,传入 null 对象时返回文本 "null"。
// 关键变化:String.valueOf(maybeNull) 将 maybeNull=null 转成文本,label 变为 "null"。
String number = String.valueOf(42);
// 返回:number 接收 String.valueOf(42) 的返回值。
// 关键变化:String.valueOf(42) 将整数 42 转成文本,number 变为 "42"。
System.out.println(label);
// 输出:null
System.out.println(number);
// 输出:42不常用但需要知道
subSequence:以 CharSequence 截取
区间仍是左闭右开,返回接口类型 CharSequence;只需要 String 时优先使用 substring。
String text = "Java速查";
// 初始状态:text 当前为 "Java速查"。
CharSequence suffix = text.subSequence(4, text.length());
// 返回:text.subSequence(4, text.length()) 截取后缀,suffix 为 "速查"。
// 作用:通过 subSequence 以 CharSequence 截取。
System.out.println(suffix);
// 输出:速查contentEquals:与其他字符序列比较
它可直接比较 StringBuilder 等 CharSequence,与 equals 的参数类型和对称性语义不同。
String text = "Java";
// 初始状态:text 当前为 "Java"。
StringBuilder builder = new StringBuilder("Java");
// 初始状态:builder 内容为 "Java"。
System.out.println(text.contentEquals(builder));
// 输出:true
// 作用:通过 contentEquals 与其他字符序列比较。regionMatches:比较局部区域
适合避免创建临时子串的局部比较;参数多且容易写错,普通场景优先 startsWith 或 substring。
String text = "Java速查";
// 初始状态:text 当前为 "Java速查"。
boolean matched = text.regionMatches(true, 0, "java", 0, 4);
// 返回:text.regionMatches(true, 0, "java", 0, 4) 忽略大小写比较前 4 个码元,matched 为 true。
// 作用:通过 regionMatches 比较局部区域。
System.out.println(matched);
// 输出:truegetChars:复制指定码元区间
按 UTF-16 码元复制到已有数组,目标空间和区间必须足够;新代码通常用 toCharArray 更直观。
String text = "Java速查";
// 初始状态:text 当前为 "Java速查"。
char[] target = new char[4];
text.getChars(0, 4, target, 0);
// 关键变化:text.getChars(0, 4, target, 0) 将 "Java" 的前 4 个码元写入 target。
// 作用:通过 getChars 复制指定码元区间。
System.out.println(new String(target));
// 输出:Javaintern:访问字符串池
返回字符串池中的规范引用,但会影响池和内存行为;业务代码不要用它替代 equals。
String dynamic = new String("Java");
// 初始状态:dynamic 当前为 new String("Java")。
String pooled = dynamic.intern();
// 返回:dynamic.intern() 返回字符串池中的 "Java" 引用,pooled == "Java" 为 true。
// 作用:通过 intern 访问字符串池。
System.out.println(pooled == "Java");
// 输出:truelines:按行流式处理
返回按换行符拆分的 Stream<String>,流只能消费一次;大量文本仍要考虑内存占用。
String text = "Java\n\nString\n集合";
// 初始状态:text 当前为 "Java\n\nString\n集合"。
long nonEmpty = text.lines().filter(line -> !line.isBlank()).count();
// 返回:text.lines().filter(line -> !line.isBlank()).count() 忽略空行,nonEmpty 为 3。
// 作用:通过 lines 按行流式处理。
System.out.println(nonEmpty);
// 输出:3indent:调整每行缩进
indent 用于给每行增加或尝试删除指定数量的前导空格,并统一行结束符。
String text = "one\ntwo";
// 初始状态:text 当前为 "one\ntwo"。
String indented = text.indent(2);
// 返回:text.indent(2) 给两行各增加 2 个空格,并在末尾保留换行符。
// 作用:通过 indent 调整每行缩进。
String visible = indented.replace("\n", "|");
System.out.println(visible);
// 输出: one| two|
System.out.println(indented.lines().count());
// 输出:2stripIndent:删除多行公共缩进
stripIndent 按文本块规则删除各非空行共有的前导空白,适合规范化多行文本。
String text = " one\n two";
// 初始状态:text 当前为 " one\n two"。
String normalized = text.stripIndent();
// 返回:text.stripIndent() 删除两行共有的 4 个前导空格,normalized 为 "one\\n two"。
// 作用:通过 stripIndent 删除多行公共缩进。
String visible = normalized.replace("\n", "|");
System.out.println(visible);
// 输出:one| two
System.out.println(normalized.lines().count());
// 输出:2translateEscapes:解析 Java 转义文本
将字符串中的 \\n、\\t 等转义变成对应字符,不等同于 JSON 解析;非法转义会抛异常。
String escaped = "Java\\nString\\t速查";
// 初始状态:escaped 当前为 "Java\\nString\\t速查"。
String actual = escaped.translateEscapes();
// 返回:escaped.translateEscapes() 将 \\n 与 \\t 变成控制字符,actual 为两行带制表符文本。
// 作用:通过 translateEscapes 解析 Java 转义文本。
System.out.println(actual.replace("\n", "|").replace("\t", "→"));
// 输出:Java|String→速查StringBuffer:同步的可变字符序列
提供与 StringBuilder 类似的同步方法,只有确实需要共享可变字符缓冲区时才考虑;普通局部拼接优先 StringBuilder。
StringBuffer shared = new StringBuffer("Java");
// 输入:shared 的初始值为 new StringBuffer("Java")。
// 作用:通过 StringBuffer 同步的可变字符序列。
shared.append("速查");
// 关键变化:shared.append("速查") 把文本追加到 "Java",shared 变为 "Java速查"。
System.out.println(shared);
// 输出:Java速查继续阅读
- 正则表达式与文本匹配:
Pattern、Matcher、捕获组和正则替换。 - JSON 与 Java 对象转换:Hutool
JSONUtil的对象、数组和泛型转换。 - 大数与精确计算:
BigInteger、BigDecimal和精确舍入。
简单案例
import java.nio.charset.StandardCharsets;
public class TextDemo {
public static void main(String[] args) {
byte[] utf8 = " 张三,Java 🙂 ".getBytes(StandardCharsets.UTF_8);
// 返回:getBytes(StandardCharsets.UTF_8) 把原文本编码为 22 个 UTF-8 字节。
String raw = new String(utf8, StandardCharsets.UTF_8);
// 返回:new String(utf8, StandardCharsets.UTF_8) 按 UTF-8 解码回 " 张三,Java 🙂 "。
String cleaned = raw.strip().replace(',', ',').replaceAll("\\s+", " ");
// 返回:strip、replace 和 replaceAll 依次清理空白、替换全角逗号并合并空白,cleaned 为 "张三,Java 🙂"。
String[] fields = cleaned.split(",", -1);
// 返回:cleaned.split(",", -1) 按逗号保留字段,fields 为 ["张三", "Java 🙂"]。
StringBuilder line = new StringBuilder();
// 初始状态:line 是空的 StringBuilder。
for (String field : fields) {
if (!line.isEmpty()) {
line.append("|");
// 关键变化:第二次循环追加分隔符 "|",line 从 "张三" 变为 "张三|"。
}
line.append(field);
// 关键变化:循环依次追加 field="张三"、"Java 🙂",line 最终为 "张三|Java 🙂"。
}
System.out.println("cleaned=" + cleaned);
// 输出:cleaned=张三,Java 🙂
System.out.println("fields=" + line + ", utf8Bytes=" + utf8.length);
// 输出:fields=张三|Java 🙂, utf8Bytes=22
System.out.println("units=" + cleaned.length() + ", codePoints=" + cleaned.codePointCount(0, cleaned.length()));
// 输出:units=10, codePoints=9
}
}案例先用指定 UTF-8 解码,再清理两端空白、全角逗号和连续空白;units 和 codePoints 对表情会出现差异。split 使用正则,逗号本身不需转义,若分隔符是点号或竖线则必须写成正则转义形式。
易混点
String不可变,replace、strip和+的返回值必须接住;StringBuilder可变,适合单线程局部拼接。==比较字符串引用身份,equals才比较内容;字符串池让错误代码有时“碰巧通过”。- UTF-8 是字节编码,UTF-16 是 Java 内部码元表示,Unicode 码点是抽象字符编号,三者不能互换。
split参数是正则而非普通字面量,且默认丢弃末尾空字段;需要保留时使用负limit。- 需要正则捕获组、预编译或复杂替换时转到正则专题,不要把
replaceAll当成普通replace。
课后小问
为什么包含表情的字符串可能出现
length() == 2而码点数为 1? 答案:该表情的一个 Unicode 码点在 UTF-16 中由一对char码元表示。 解析:length()数的是码元;需要按码点遍历时使用codePointCount或codePoints(),用户可见字素还可能需要更高层处理。把
split("|")当作按竖线切分有什么问题? 答案:|在正则中表示“或”,不是普通竖线;应写成split("\\|")或使用Pattern.quote("|")。 解析:split接受正则表达式,元字符必须转义,否则匹配语义与字面分隔符不同,清洗结果会被错误拆分。
本节小结
String不可变,循环拼接应考虑StringBuilder,内容比较使用equals。- 字符串池只影响共享与身份,不能改变内容比较的规则。
- UTF-8、UTF-16 码元和 Unicode 码点位于不同抽象层,长度含义必须说清。
replace按字面替换,split等 API 按正则解释;复杂匹配转到独立正则专题。
快速回顾
- 能说明不可变字符串为何会产生新对象。
- 能为字节与字符串转换显式选择
StandardCharsets.UTF_8。 - 能区分
length()、码点数量和用户可见字符数量。 - 能发现
split正则元字符与limit参数带来的差异。 - 能按字面匹配、Unicode 码点和显式字符集选择合适的 String API。
阿源的知识库