强曰为道
与天地相似,故不违。知周乎万物,而道济天下,故不过。旁行而不流,乐天知命,故不忧.
文档目录

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

维度StringBuilderStringBuffer
线程安全❌ 非线程安全✅ synchronized
性能更快较慢(同步开销)
推荐场景单线程多线程共享
API完全相同完全相同

💡 日常开发优先使用 StringBuilderStringBuffer 只在多线程共享时使用。

字符串格式化

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.bacb, 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?rcolor, colour
{n,m}n 到 m 次\d{3,5}123, 12345
[abc]字符集[aeiou] → 元音
[^abc]排除字符集[^0-9] → 非数字
^行首^Hello
$行尾World$
()分组(\\d{4})-(\\d{2})
\\1反向引用(\\w+)\\s+\\1hello hello
(?i)忽略大小写(?i)helloHELLO
(?=...)前瞻断言\\d(?=元) → 匹配 55元

⚠️ 注意事项

  1. 字符串比较永远用 equals()== 比较的是引用。
  2. null 安全 — 调用方法前检查 null,或使用 Objects.toString(obj, default)
  3. 正则预编译 — 循环中使用的正则应提前 Pattern.compile()
  4. substring() 在 JDK 7+ 不共享底层数组 — 不会内存泄漏。
  5. split() 参数是正则 — 特殊字符需要转义:split("\\.")

💡 技巧

  1. String.join 拼接集合

    List<String> words = List.of("Hello", "World");
    String result = String.join(" ", words);  // "Hello World"
    
  2. 重复字符串(JDK 11+)

    String line = "-".repeat(50);  // "--------------------------------------------------"
    String star = "*".repeat(5);   // "*****"
    
  3. 字符串缩进(JDK 12+)

    String indented = "Hello\nWorld".indent(4);  // "    Hello\n    World\n"
    
  4. null 安全的字符串操作

    String s = null;
    int len = Optional.ofNullable(s).map(String::length).orElse(0);
    

🏢 业务场景

  • 数据验证: 正则验证手机号、邮箱、身份证号。
  • 日志解析: 正则提取日志中的关键信息。
  • SQL 拼接: 使用 StringBuilder 构建动态 SQL。
  • 模板渲染: 使用 String.format()MessageFormat 生成文本。
  • JSON 处理: 文本块定义 JSON 模板,格式化填充数据。

📖 扩展阅读