Java 完全指南 / 09 - 字符串:String、StringBuilder、格式化、正则
09 - 字符串:String、StringBuilder、格式化、正则
String 基础
public class StringBasics {
public static void main(String[] args) {
// 创建字符串
String s1 = "Hello"; // 字面量(推荐,放入字符串常量池)
String s2 = new String("Hello"); // 新对象(不推荐)
char[] chars = {'H', 'e', 'l', 'l', 'o'};
String s3 = new String(chars); // 从字符数组
// 不可变性
String s = "Hello";
s = s + " World"; // 创建了新对象,原 "Hello" 仍在常量池
System.out.println(s); // Hello World
// 字符串比较
String a = "hello";
String b = "hello";
String c = new String("hello");
System.out.println(a == b); // true(常量池同一对象)
System.out.println(a == c); // false(不同对象)
System.out.println(a.equals(c)); // true(值相等)
System.out.println(a.compareTo(c)); // 0(字典序比较)
// intern() —— 返回常量池中的引用
String d = c.intern();
System.out.println(a == d); // true
}
}
String 常用方法
| 方法 | 说明 | 示例 |
|---|---|---|
length() | 长度 | "abc".length() → 3 |
charAt(i) | 指定位置字符 | "abc".charAt(1) → 'b' |
substring(begin) | 截取子串 | "hello".substring(2) → "llo" |
substring(begin, end) | 截取范围 | "hello".substring(1,3) → "el" |
indexOf(str) | 查找位置 | "hello".indexOf("ll") → 2 |
lastIndexOf(str) | 末次查找 | "hello".lastIndexOf("l") → 3 |
contains(str) | 是否包含 | "hello".contains("ell") → true |
startsWith(str) | 前缀判断 | "hello".startsWith("he") → true |
endsWith(str) | 后缀判断 | "hello".endsWith("lo") → true |
toUpperCase() | 转大写 | "hello".toUpperCase() → "HELLO" |
toLowerCase() | 转小写 | |
trim() | 去首尾空格 | " hi ".trim() → "hi" |
strip() | 去首尾空格(JDK11+) | 支持 Unicode 空格 |
replace(old, new) | 替换 | "hello".replace("l","L") → "heLLo" |
split(regex) | 分割 | "a,b,c".split(",") → ["a","b","c"] |
join(delim, ...) | 拼接 | String.join("-","a","b") → "a-b" |
isEmpty() | 是否空串 | "".isEmpty() → true |
isBlank() | 是否空白(JDK11+) | " ".isBlank() → true |
toCharArray() | 转字符数组 | |
valueOf(x) | 转字符串 | String.valueOf(42) → "42" |
public class StringMethods {
public static void main(String[] args) {
String text = " Hello, World! Hello, Java! ";
// 常用操作
System.out.println(text.trim()); // Hello, World! Hello, Java!
System.out.println(text.strip()); // Hello, World! Hello, Java!
System.out.println(text.stripLeading()); // Hello, World! Hello, Java!
System.out.println(text.stripTrailing()); // Hello, World! Hello, Java!
// 查找与替换
System.out.println(text.indexOf("Hello")); // 2
System.out.println(text.lastIndexOf("Hello")); // 17
System.out.println(text.contains("World")); // true
System.out.println(text.replace("Hello", "Hi")); // Hi, World! Hi, Java!
// 分割与拼接
String csv = "苹果,香蕉,橘子,西瓜";
String[] fruits = csv.split(",");
for (String f : fruits) {
System.out.println(" - " + f.trim());
}
System.out.println(String.join(" | ", fruits)); // 苹果 | 香蕉 | 橘子 | 西瓜
// 类型转换
String num = String.valueOf(3.14);
int i = Integer.parseInt("42");
double d = Double.parseDouble("3.14");
// 判空
System.out.println("".isEmpty()); // true
System.out.println(" ".isBlank()); // true(JDK 11+)
System.out.println(" ".isEmpty()); // false
}
}
字符串不可变性
// String 是不可变的(Immutable)
// 每次修改都创建新对象,原对象不变
String s = "Hello";
s.concat(" World"); // 返回新对象 "Hello World",但 s 没变
s = s.concat(" World"); // 现在 s 指向新对象
// 不可变的好处:
// 1. 线程安全
// 2. 可以缓存 hashCode
// 3. 字符串常量池复用
// 4. 安全性(网络连接、文件路径等不能被篡改)
⚠️ 循环中拼接字符串不要用
+,会创建大量临时对象。
StringBuilder 与 StringBuffer
public class StringBuilderDemo {
public static void main(String[] args) {
// StringBuilder —— 可变字符串(非线程安全,推荐单线程使用)
StringBuilder sb = new StringBuilder();
sb.append("Hello");
sb.append(", ");
sb.append("World");
sb.append('!');
System.out.println(sb.toString()); // Hello, World!
System.out.println(sb.length()); // 13
System.out.println(sb.capacity()); // 初始16 + 扩容
// 链式调用
String result = new StringBuilder()
.append("姓名: ")
.append("张三")
.append(", 年龄: ")
.append(25)
.append(", 分数: ")
.append(String.format("%.1f", 92.5))
.toString();
System.out.println(result);
// 插入与删除
sb.insert(5, " Beautiful");
System.out.println(sb); // Hello Beautiful, World!
sb.delete(5, 15);
System.out.println(sb); // Hello, World!
// 反转
StringBuilder rev = new StringBuilder("abcdef");
System.out.println(rev.reverse()); // fedcba
// 性能对比
long start = System.nanoTime();
String slow = "";
for (int i = 0; i < 100000; i++) {
slow += "a"; // 每次创建新 String 对象
}
long mid = System.nanoTime();
StringBuilder fast = new StringBuilder();
for (int i = 0; i < 100000; i++) {
fast.append("a"); // 原地修改
}
long end = System.nanoTime();
System.out.printf("String拼接: %d ms%n", (mid - start) / 1_000_000);
System.out.printf("StringBuilder: %d ms%n", (end - mid) / 1_000_000);
// StringBuilder 通常快 100-1000 倍
}
}
StringBuilder vs StringBuffer
| 维度 | StringBuilder | StringBuffer |
|---|---|---|
| 线程安全 | ❌ 非线程安全 | ✅ synchronized |
| 性能 | 更快 | 较慢(同步开销) |
| 推荐场景 | 单线程 | 多线程共享 |
| API | 完全相同 | 完全相同 |
💡 日常开发优先使用
StringBuilder。StringBuffer只在多线程共享时使用。
字符串格式化
public class FormatDemo {
public static void main(String[] args) {
// String.format()
String s1 = String.format("姓名: %s, 年龄: %d", "张三", 25);
String s2 = String.format("圆周率: %.4f", Math.PI);
String s3 = String.format("十六进制: %X", 255);
String s4 = String.format("百分比: %.1f%%", 85.6);
System.out.println(s1); // 姓名: 张三, 年龄: 25
System.out.println(s2); // 圆周率: 3.1416
System.out.println(s3); // 十六进制: FF
System.out.println(s4); // 百分比: 85.6%
// 格式化对齐
System.out.printf("%-10s | %5d | %.2f%n", "苹果", 15, 3.99);
System.out.printf("%-10s | %5d | %.2f%n", "香蕉", 8, 2.50);
System.out.printf("%-10s | %5d | %.2f%n", "西瓜", 3, 12.80);
// 苹果 | 15 | 3.99
// 香蕉 | 8 | 2.50
// 西瓜 | 3 | 12.80
// 格式化占位符
// %s 字符串
// %d 整数
// %f 浮点数
// %e 科学记数法
// %x/%X 十六进制
// %o 八进制
// %b 布尔
// %c 字符
// %n 换行(跨平台)
// %% 百分号
// 文本块格式化(JDK 15+)
String table = """
+----------+-----+------+
| 商品 | 数量 | 价格 |
+----------+-----+------+
| %-8s | %3d | %4.1f |
| %-8s | %3d | %4.1f |
+----------+-----+------+
""".formatted("苹果", 15, 3.99, "香蕉", 8, 2.50);
System.out.println(table);
// MessageFormat(国际化格式化)
java.text.MessageFormat mf = new java.text.MessageFormat(
"{0} 你好,你的余额为 {1,number,currency}");
System.out.println(mf.format(new Object[]{"张三", 12345.67}));
// 张三 你好,你的余额为 ¥12,345.67
}
}
文本块(Text Blocks,JDK 15+)
public class TextBlockDemo {
public static void main(String[] args) {
// 传统写法
String json = "{\n" +
" \"name\": \"张三\",\n" +
" \"age\": 25\n" +
"}";
// 文本块写法
String jsonBlock = """
{
"name": "张三",
"age": 25
}
""";
// HTML
String html = """
<html>
<head><title>Java</title></head>
<body>
<h1>Hello, World!</h1>
</body>
</html>
""";
// SQL
String sql = """
SELECT u.name, u.age, d.name AS dept
FROM users u
JOIN departments d ON u.dept_id = d.id
WHERE u.age > 18
ORDER BY u.name
""";
System.out.println(jsonBlock);
System.out.println(html);
System.out.println(sql);
}
}
正则表达式
import java.util.regex.*;
public class RegexDemo {
public static void main(String[] args) {
// 基本匹配
String email = "user@example.com";
String emailRegex = "^[\\w.-]+@[\\w.-]+\\.[a-zA-Z]{2,}$";
System.out.println("邮箱验证: " + email.matches(emailRegex));
// Pattern 与 Matcher
String text = "我的手机号是 13812345678,备用 15987654321";
Pattern phonePattern = Pattern.compile("1[3-9]\\d{9}");
Matcher matcher = phonePattern.matcher(text);
while (matcher.find()) {
System.out.println("找到手机号: " + matcher.group()
+ " 位置: [" + matcher.start() + "," + matcher.end() + "]");
}
// 分组提取
String dateStr = "2024-06-15";
Pattern datePattern = Pattern.compile("(\\d{4})-(\\d{2})-(\\d{2})");
Matcher dm = datePattern.matcher(dateStr);
if (dm.matches()) {
System.out.println("年: " + dm.group(1)); // 2024
System.out.println("月: " + dm.group(2)); // 06
System.out.println("日: " + dm.group(3)); // 15
}
// 常用正则
String[][] patterns = {
{"手机号", "^1[3-9]\\d{9}$"},
{"邮箱", "^[\\w.-]+@[\\w.-]+\\.\\w+$"},
{"身份证", "^\\d{17}[\\dX]$"},
{"IP地址", "^((25[0-5]|2[0-4]\\d|[01]?\\d\\d?)\\.){3}(25[0-5]|2[0-4]\\d|[01]?\\d\\d?)$"},
{"URL", "^https?://[\\w.-]+(/.*)?$"},
{"中文", "^[\\u4e00-\\u9fa5]+$"},
};
// 替换
String censored = "手机 13812345678 座机 010-88886666"
.replaceAll("1[3-9]\\d{9}", "***");
System.out.println("脱敏: " + censored);
// 分割
String[] parts = "a,,b,,c".split(",+");
System.out.println(java.util.Arrays.toString(parts)); // [a, b, c]
// 预编译(性能优化:重复使用时务必预编译)
Pattern compiled = Pattern.compile("\\d+");
for (String s : new String[]{"abc123", "456def", "ghi"}) {
Matcher m = compiled.matcher(s);
if (m.find()) {
System.out.println(s + " 中的数字: " + m.group());
}
}
}
}
常用正则模式
| 模式 | 说明 | 示例匹配 |
|---|---|---|
. | 任意字符 | a.b → acb, a1b |
\d | 数字 [0-9] | \d+ → 123 |
\w | 单词字符 [a-zA-Z0-9_] | \w+ → hello_1 |
\s | 空白字符 | \s+ → \t |
* | 0 次或多次 | ab* → a, abbb |
+ | 1 次或多次 | ab+ → ab, abbb |
? | 0 次或 1 次 | colou?r → color, colour |
{n,m} | n 到 m 次 | \d{3,5} → 123, 12345 |
[abc] | 字符集 | [aeiou] → 元音 |
[^abc] | 排除字符集 | [^0-9] → 非数字 |
^ | 行首 | ^Hello |
$ | 行尾 | World$ |
() | 分组 | (\\d{4})-(\\d{2}) |
\\1 | 反向引用 | (\\w+)\\s+\\1 → hello hello |
(?i) | 忽略大小写 | (?i)hello → HELLO |
(?=...) | 前瞻断言 | \\d(?=元) → 匹配 5 在 5元 中 |
⚠️ 注意事项
- 字符串比较永远用
equals()—==比较的是引用。 null安全 — 调用方法前检查null,或使用Objects.toString(obj, default)。- 正则预编译 — 循环中使用的正则应提前
Pattern.compile()。 substring()在 JDK 7+ 不共享底层数组 — 不会内存泄漏。split()参数是正则 — 特殊字符需要转义:split("\\.")。
💡 技巧
String.join 拼接集合:
List<String> words = List.of("Hello", "World"); String result = String.join(" ", words); // "Hello World"重复字符串(JDK 11+):
String line = "-".repeat(50); // "--------------------------------------------------" String star = "*".repeat(5); // "*****"字符串缩进(JDK 12+):
String indented = "Hello\nWorld".indent(4); // " Hello\n World\n"null 安全的字符串操作:
String s = null; int len = Optional.ofNullable(s).map(String::length).orElse(0);
🏢 业务场景
- 数据验证: 正则验证手机号、邮箱、身份证号。
- 日志解析: 正则提取日志中的关键信息。
- SQL 拼接: 使用
StringBuilder构建动态 SQL。 - 模板渲染: 使用
String.format()或MessageFormat生成文本。 - JSON 处理: 文本块定义 JSON 模板,格式化填充数据。