String.replaceAll() 用正则表达式替换所有匹配项

方法定义

public String replaceAll(String regex, String replacement)

参数说明

  • regex:用于匹配的正则表达式
  • replacement:替换匹配项的字符串

返回值

  • 返回一个新字符串,其中所有与正则表达式匹配的部分都被替换
  • 原始字符串保持不变(字符串是不可变的)

功能说明

replaceAll() 方法使用正则表达式查找字符串中所有匹配的部分,并用指定的替换字符串替换它们。这是与 replace() 方法的关键区别:

  • replace():按字面值替换,不使用正则表达式
  • replaceAll():使用正则表达式进行模式匹配

重要特性

  • 返回新字符串,原字符串不变
  • 替换所有匹配项(不仅仅是第一个)
  • 正则表达式会被编译和匹配
  • 替换字符串中的 $\ 有特殊含义

示例代码

基本用法

public class ReplaceAllExample {
    public static void main(String[] args) {
        String text = "Hello world, hello Java, HELLO Programming";
        
        // 忽略大小写的替换
        String result1 = text.replaceAll("(?i)hello", "Hi");
        System.out.println(result1); 
        // "Hi world, Hi Java, Hi Programming"
        
        // 替换所有数字
        String numberText = "Phone: 123-456-7890, Age: 25";
        String result2 = numberText.replaceAll("\\d", "*");
        System.out.println(result2);
        // "Phone: ***-***-****, Age: **"
        
        // 替换所有空白字符
        String messyText = "  Hello    World  \t\n  Java  ";
        String result3 = messyText.replaceAll("\\s+", " ");
        System.out.println("'" + result3 + "'");
        // " Hello World Java "
    }
}

常见应用场景

// 1. 清理文本:移除特殊字符
String dirtyText = "User@name#123!password$";
String cleanText = dirtyText.replaceAll("[^a-zA-Z0-9]", "");
System.out.println(cleanText); // "Username123password"

// 2. 格式化电话号码
String phone = "123-456-7890";
String formatted = phone.replaceAll("(\\d{3})-(\\d{3})-(\\d{4})", "($1) $2-$3");
System.out.println(formatted); // "(123) 456-7890"

// 3. HTML 转义字符处理
String html = "<p>Hello & World</p>";
String escaped = html.replaceAll("[<>&]", 
    match -> {
        switch (match) {
            case "<": return "&lt;";
            case ">": return "&gt;";
            case "&": return "&amp;";
            default: return match;
        }
    });
// 注意:上面是 Java 8+ 的方式,传统方式需要多次 replaceAll

// 4. 邮箱地址模糊处理
String email = "john.doe@example.com";
String masked = email.replaceAll("(?<=.).(?=.*@)", "*");
System.out.println(masked); // "j***n.d**@example.com"

使用捕获组

// 日期格式转换:YYYY-MM-DD to DD/MM/YYYY
String date = "2023-12-25";
String newFormat = date.replaceAll("(\\d{4})-(\\d{2})-(\\d{2})", "$3/$2/$1");
System.out.println(newFormat); // "25/12/2023"

// 交换字符串中的两个部分
String name = "Doe, John";
String formattedName = name.replaceAll("(\\w+),\\s+(\\w+)", "$2 $1");
System.out.println(formattedName); // "John Doe"

使用技巧

1. 转义特殊字符

// 错误:点号在正则中表示任意字符
// String result = "file.txt".replaceAll(".", "X"); // 返回 "XXXXX"

// 正确:转义点号
String result = "file.txt".replaceAll("\\.", "X");
System.out.println(result); // "fileXtxt"

// 需要转义的字符:. [ { ( ) * + ? ^ $ \ | 
String escaped = "a.b*c".replaceAll("[.\\*]", "X");
System.out.println(escaped); // "aXbXc"

2. 处理替换字符串中的特殊字符

// 替换字符串中的 $ 和 \ 需要特殊处理
String text = "Price: $100";
// 错误:$1 被解释为捕获组
// String result = text.replaceAll("\\$(\\d+)", "€$1"); // 可能出错

// 正确:使用 Matcher.quoteReplacement()
String euro = Matcher.quoteReplacement("€") + "$1";
String result = text.replaceAll("\\$(\\d+)", euro);
System.out.println(result); // "Price: €100"

// 或者使用 replace() 方法(如果不需要正则)
String simpleReplace = text.replace("$", "€");

3. 忽略大小写替换

// 使用 (?i) 标志进行忽略大小写匹配
String text = "Java JAVA java";
String result = text.replaceAll("(?i)java", "Python");
System.out.println(result); // "Python Python Python"

4. 限制替换次数的技巧

// replaceAll() 替换所有,如果只想替换前N个,需要自定义方法
public static String replaceFirstN(String text, String regex, String replacement, int n) {
    Pattern pattern = Pattern.compile(regex);
    Matcher matcher = pattern.matcher(text);
    
    StringBuffer sb = new StringBuffer();
    int count = 0;
    
    while (matcher.find() && count < n) {
        matcher.appendReplacement(sb, replacement);
        count++;
    }
    matcher.appendTail(sb);
    
    return sb.toString();
}

// 使用示例
String text = "a-b-c-d-e";
String result = replaceFirstN(text, "-", "_", 2);
System.out.println(result); // "a_b_c-d-e"

常见错误

1. 忘记转义正则特殊字符

// 错误示例
String result = "1.5 + 2.3 = 3.8".replaceAll(".", "X");
// 结果是 "XXXXXXXXXXXXX",因为 . 匹配所有字符

// 正确做法
String result = "1.5 + 2.3 = 3.8".replaceAll("\\.", "X");
// 结果是 "1X5 + 2X3 = 3X8"

2. 替换字符串中的 $ 问题

// 错误:$ 被解释为捕获组
String text = "The price is $10";
// String result = text.replaceAll("\\$(\\d+)", "Cost: $1"); // 可能抛出异常

// 正确:使用 Matcher.quoteReplacement()
String replacement = Matcher.quoteReplacement("Cost: $") + "$1";
String result = text.replaceAll("\\$(\\d+)", replacement);

3. 性能问题:在循环中使用

// 不推荐:在循环中重复编译正则表达式
List<String> texts = Arrays.asList("...", "..."); 
for (String text : texts) {
    String result = text.replaceAll("\\d+", "X"); // 每次都编译
}

// 推荐:预编译模式
Pattern pattern = Pattern.compile("\\d+");
for (String text : texts) {
    String result = pattern.matcher(text).replaceAll("X");
}

4. 空指针异常

// 错误:null 字符串调用方法
// String nullText = null;
// nullText.replaceAll("a", "b"); // NullPointerException

// 正确:添加 null 检查
public static String safeReplaceAll(String text, String regex, String replacement) {
    if (text == null) return null;
    return text.replaceAll(regex, replacement);
}

注意事项

  1. 正则表达式语法:确保正则表达式语法正确,否则会抛出 PatternSyntaxException
  2. 性能考虑:正则表达式编译有开销,频繁使用时考虑预编译
  3. 内存使用:会产生新字符串对象,大量操作时注意内存
  4. 线程安全:字符串本身是不可变的,但正则模式可以共享
  5. 特殊字符:注意替换字符串中 $\ 的特殊含义

最佳实践

1. 预编译正则表达式

// 对于频繁使用的正则表达式
private static final Pattern EMAIL_PATTERN = Pattern.compile("[a-zA-Z0-9._%+-]+@[a-zA-Z0-9.-]+\\.[a-zA-Z]{2,}");

public static String maskEmail(String email) {
    return EMAIL_PATTERN.matcher(email).replaceAll("****@****.com");
}

2. 创建工具方法

public class StringUtils {
    // 安全的 replaceAll 方法
    public static String safeReplaceAll(String text, String regex, String replacement) {
        if (text == null || regex == null || replacement == null) {
            return text;
        }
        try {
            return text.replaceAll(regex, replacement);
        } catch (PatternSyntaxException e) {
            // 处理正则表达式错误
            System.err.println("Invalid regex: " + regex);
            return text;
        }
    }
    
    // 转义文本用于正则表达式
    public static String escapeForRegex(String text) {
        return Pattern.quote(text);
    }
}

3. 使用 StringBuilder 进行多次替换

// 对于多个替换操作
public static String multiReplace(String text, Map<String, String> replacements) {
    StringBuilder sb = new StringBuilder(text);
    
    for (Map.Entry<String, String> entry : replacements.entrySet()) {
        String regex = Pattern.quote(entry.getKey()); // 转义字面值
        String escapedReplacement = Matcher.quoteReplacement(entry.getValue());
        
        Pattern pattern = Pattern.compile(regex);
        Matcher matcher = pattern.matcher(sb);
        
        sb = new StringBuilder();
        while (matcher.find()) {
            matcher.appendReplacement(sb, escapedReplacement);
        }
        matcher.appendTail(sb);
    }
    
    return sb.toString();
}

4. 验证输入

public static boolean isValidRegex(String regex) {
    try {
        Pattern.compile(regex);
        return true;
    } catch (PatternSyntaxException e) {
        return false;
    }
}

性能优化

1. 预编译模式对象

// 创建静态模式对象
private static final Pattern DIGIT_PATTERN = Pattern.compile("\\d+");
private static final Pattern WORD_PATTERN = Pattern.compile("\\w+");

// 使用预编译的模式
public static String processText(String text) {
    return DIGIT_PATTERN.matcher(text).replaceAll("#")
           .replaceAll(WORD_PATTERN, "WORD");
}

2. 对于简单替换,考虑使用 replace()

// 如果只是字面值替换,使用 replace() 更高效
String text = "Hello world";
// 使用 replace() - 更快
String result1 = text.replace("world", "Java");
// 使用 replaceAll() - 较慢(需要正则编译)
String result2 = text.replaceAll("world", "Java");

3. 批量处理

// 对于大量文本的相同替换
public static List<String> batchReplace(List<String> texts, String regex, String replacement) {
    Pattern pattern = Pattern.compile(regex);
    return texts.parallelStream()  // 使用并行流
                .map(text -> pattern.matcher(text).replaceAll(replacement))
                .collect(Collectors.toList());
}

总结

String.replaceAll() 是 Java 中强大的字符串替换工具,关键要点:

  1. 核心功能:使用正则表达式替换所有匹配项
  2. 正则表达式:需要正确理解和转义特殊字符
  3. 不可变性:返回新字符串,原字符串不变
  4. 特殊字符:注意替换字符串中 $\ 的特殊含义
  5. 最佳实践
    • 正确转义正则特殊字符
    • 对于频繁操作,预编译正则表达式
    • 处理异常情况和边界条件
    • 考虑性能优化

掌握 replaceAll() 方法能有效处理各种复杂的字符串替换需求,特别是在数据清洗、格式化和文本处理场景中非常有用。