MK
摩柯社区 - 一个极简的技术知识社区
AI 面试

Java String 的正则表达式应用

2023-09-301.5k 阅读

Java String 中正则表达式的基础概念

什么是正则表达式

正则表达式是一种描述字符模式的强大工具,在Java中它被广泛用于字符串的匹配、查找、替换等操作。简单来说,正则表达式是由普通字符(例如字母 az)以及特殊字符(称为元字符)组成的文字模式。这些模式描述在搜索文本时要匹配的一个或多个字符串。例如,模式 abc 会匹配字符串中出现的 “abc” 子串,而模式 a.c 中的点号(.)是一个元字符,它可以匹配任何单个字符,所以这个模式可以匹配 “aac”、“abc”、“adc” 等。

Java 对正则表达式的支持

在Java中,对正则表达式的支持主要通过 java.util.regex 包来实现。这个包包含了 PatternMatcher 两个核心类。Pattern 类表示一个编译后的正则表达式,Matcher 类则用于在输入字符串上执行匹配操作。此外,String 类本身也提供了一些方便的方法来使用正则表达式,如 matchessplitreplaceAll 等。这些方法内部其实也是调用了 PatternMatcher 类的功能。

正则表达式的基本语法

  1. 字符类:字符类用于匹配一组字符中的任意一个。例如,[abc] 表示匹配 abc 中的任意一个字符。方括号内还可以使用范围,如 [a - z] 表示匹配任意小写字母,[0 - 9] 表示匹配任意数字。如果要匹配除了某些字符之外的字符,可以在方括号内使用 ^ 符号,如 [^abc] 表示匹配除了 abc 之外的任意字符。
  2. 预定义字符类:Java提供了一些预定义的字符类,使用起来更加方便。例如,\d 等价于 [0 - 9],表示匹配任意数字;\w 等价于 [a - zA - Z0 - 9_],表示匹配任意字母、数字或下划线;\s 表示匹配任意空白字符,包括空格、制表符、换行符等。对应的大写形式则表示相反的含义,如 \D 表示匹配任意非数字字符,\W 表示匹配任意非字母、数字或下划线的字符,\S 表示匹配任意非空白字符。
  3. 量词:量词用于指定前面的字符或字符类出现的次数。例如,X? 表示 X 出现0次或1次;X* 表示 X 出现0次或多次;X+ 表示 X 出现1次或多次。还可以指定具体的次数范围,如 X{n} 表示 X 恰好出现 n 次,X{n,} 表示 X 至少出现 n 次,X{n,m} 表示 X 出现 nm 次(包括 nm)。
  4. 分组:使用圆括号 () 可以将多个字符或字符类组合成一个组。组可以像单个字符一样应用量词,并且可以在匹配后通过 Matcher 类的方法获取组内的匹配结果。例如,(abc)+ 表示 “abc” 子串出现1次或多次。

使用 String 类的方法应用正则表达式

matches 方法

String 类的 matches 方法用于判断当前字符串是否完全匹配给定的正则表达式。其语法为 public boolean matches(String regex)。例如,要判断一个字符串是否是有效的手机号码,可以使用如下代码:

public class MobileNumberMatcher {
    public static void main(String[] args) {
        String mobileNumber = "13800138000";
        String regex = "^1[3 - 9]\\d{9}$";
        boolean isMatch = mobileNumber.matches(regex);
        if (isMatch) {
            System.out.println("这是一个有效的手机号码");
        } else {
            System.out.println("这不是一个有效的手机号码");
        }
    }
}

在上述代码中,^ 表示字符串的开始位置,$ 表示字符串的结束位置。所以 ^1[3 - 9]\\d{9}$ 这个正则表达式表示以 1 开头,第二位是 39 中的任意一个数字,后面跟着9个数字,这样就完整地匹配了常见的11位手机号码格式。

split 方法

split 方法用于根据正则表达式将字符串拆分成字符串数组。其语法有两种形式:public String[] split(String regex)public String[] split(String regex, int limit)。第一种形式将字符串按照正则表达式匹配的部分进行拆分,返回拆分后的字符串数组。第二种形式的 limit 参数用于控制拆分的次数,最多拆分 limit - 1 次,数组的长度不会超过 limit,并且如果 limit 为负数,则不限拆分次数。例如,要将一个以逗号分隔的字符串拆分成单词数组,可以这样做:

public class StringSplitter {
    public static void main(String[] args) {
        String text = "apple,banana,cherry";
        String regex = ",";
        String[] words = text.split(regex);
        for (String word : words) {
            System.out.println(word);
        }
    }
}

上述代码中,通过逗号作为正则表达式,将字符串拆分成了三个单词。如果要按照多个分隔符拆分,例如同时以逗号和空格作为分隔符,可以使用字符类 [,\s],如下所示:

public class StringMultiSplitter {
    public static void main(String[] args) {
        String text = "apple, banana,cherry";
        String regex = "[,\s]";
        String[] words = text.split(regex);
        for (String word : words) {
            if (!word.isEmpty()) {
                System.out.println(word);
            }
        }
    }
}

在这个例子中,[,\s] 表示匹配逗号或者空白字符,这样就可以将字符串按照逗号和空格进行拆分。注意在遍历数组时,我们检查了是否为空字符串,以避免输出空的结果。

replaceAll 方法

replaceAll 方法用于将字符串中所有匹配正则表达式的子串替换为指定的字符串。其语法为 public String replaceAll(String regex, String replacement)。例如,要将字符串中的所有数字替换为 X,可以使用以下代码:

public class NumberReplacer {
    public static void main(String[] args) {
        String text = "abc123def456";
        String regex = "\\d+";
        String replacedText = text.replaceAll(regex, "X");
        System.out.println(replacedText);
    }
}

在上述代码中,\\d+ 表示匹配一个或多个数字,replaceAll 方法将所有匹配到的数字子串替换为 X,输出结果为 “abcXdefX”。如果要进行更复杂的替换,例如根据不同的匹配情况进行不同的替换,可以结合 Matcher 类和 Pattern 类来实现。

Pattern 和 Matcher 类的深入应用

Pattern 类

Pattern 类表示一个编译后的正则表达式。在使用 Pattern 类时,首先需要通过 Pattern.compile(String regex) 方法将正则表达式编译成 Pattern 对象。编译后的 Pattern 对象可以被多次重用,这在需要对多个字符串进行相同正则表达式匹配的场景下非常有用,能够提高效率。例如:

import java.util.regex.Pattern;

public class PatternExample {
    public static void main(String[] args) {
        String regex = "\\d+";
        Pattern pattern = Pattern.compile(regex);
        // 这里可以用 pattern 对多个字符串进行匹配操作
    }
}

Pattern 类还提供了一些静态方法,如 Pattern.matches(String regex, CharSequence input),这个方法相当于先编译正则表达式,然后调用 input.matches(regex),是一种便捷的写法。但如果需要多次使用同一个正则表达式,还是建议先编译成 Pattern 对象。

Matcher 类

Matcher 类用于在输入字符串上执行匹配操作。通过 Pattern 对象的 matcher(CharSequence input) 方法可以创建一个 Matcher 对象。Matcher 类提供了多个方法来进行匹配操作,其中最常用的是 find()group() 方法。

  1. find() 方法find() 方法用于尝试在输入字符串中查找下一个匹配正则表达式的子串。如果找到匹配的子串,则返回 true,并且可以通过 group() 方法获取该匹配子串。例如,要在一个字符串中查找所有的数字子串:
import java.util.regex.Matcher;
import java.util.regex.Pattern;

public class NumberFinder {
    public static void main(String[] args) {
        String text = "abc123def456";
        String regex = "\\d+";
        Pattern pattern = Pattern.compile(regex);
        Matcher matcher = pattern.matcher(text);
        while (matcher.find()) {
            System.out.println("找到数字子串: " + matcher.group());
        }
    }
}

在上述代码中,while (matcher.find()) 循环会不断查找字符串中的数字子串,每次找到后通过 matcher.group() 获取并输出。 2. group() 方法group() 方法有多种重载形式。无参数的 group() 方法返回上一次匹配操作找到的整个匹配子串。如果在正则表达式中使用了分组,group(int group) 方法可以返回指定组的匹配结果,组号从1开始。例如,对于正则表达式 (\d+)([a - z]+)group(1) 将返回第一个组(即数字部分)的匹配结果,group(2) 将返回第二个组(即字母部分)的匹配结果。下面是一个示例:

import java.util.regex.Matcher;
import java.util.regex.Pattern;

public class GroupMatcher {
    public static void main(String[] args) {
        String text = "123abc456def";
        String regex = "(\d+)([a - z]+)";
        Pattern pattern = Pattern.compile(regex);
        Matcher matcher = pattern.matcher(text);
        while (matcher.find()) {
            System.out.println("数字部分: " + matcher.group(1));
            System.out.println("字母部分: " + matcher.group(2));
        }
    }
}

在这个例子中,通过分组分别获取了数字和字母部分的匹配结果。

Matcher 类的其他重要方法

  1. lookingAt() 方法lookingAt() 方法尝试从输入字符串的起始位置开始匹配正则表达式。与 find() 方法不同,lookingAt() 方法只会从字符串的开头开始匹配,如果开头不匹配则返回 false,而不会继续查找后续位置。例如:
import java.util.regex.Matcher;
import java.util.regex.Pattern;

public class LookingAtExample {
    public static void main(String[] args) {
        String text = "abc123";
        String regex = "abc";
        Pattern pattern = Pattern.compile(regex);
        Matcher matcher = pattern.matcher(text);
        boolean result = matcher.lookingAt();
        System.out.println(result); // 输出 true
    }
}
  1. matches() 方法Matcher 类的 matches() 方法用于判断整个输入字符串是否完全匹配正则表达式,这与 String 类的 matches 方法功能类似。例如:
import java.util.regex.Matcher;
import java.util.regex.Pattern;

public class MatcherMatchesExample {
    public static void main(String[] args) {
        String text = "123";
        String regex = "\\d+";
        Pattern pattern = Pattern.compile(regex);
        Matcher matcher = pattern.matcher(text);
        boolean result = matcher.matches();
        System.out.println(result); // 输出 true
    }
}
  1. replaceFirst() 方法replaceFirst(String replacement) 方法用于将字符串中第一个匹配正则表达式的子串替换为指定的字符串。例如:
import java.util.regex.Matcher;
import java.util.regex.Pattern;

public class ReplaceFirstExample {
    public static void main(String[] args) {
        String text = "abc123def123";
        String regex = "\\d+";
        Pattern pattern = Pattern.compile(regex);
        Matcher matcher = pattern.matcher(text);
        String replacedText = matcher.replaceFirst("X");
        System.out.println(replacedText); // 输出 abcXdef123
    }
}
  1. replaceAll() 方法Matcher 类的 replaceAll(String replacement) 方法与 String 类的 replaceAll 方法类似,都是将所有匹配正则表达式的子串替换为指定字符串。但 Matcher 类的这个方法在需要更复杂替换逻辑时更具灵活性,因为可以结合 Matcher 的其他方法来实现。例如:
import java.util.regex.Matcher;
import java.util.regex.Pattern;

public class MatcherReplaceAllExample {
    public static void main(String[] args) {
        String text = "abc123def456";
        String regex = "\\d+";
        Pattern pattern = Pattern.compile(regex);
        Matcher matcher = pattern.matcher(text);
        String replacedText = matcher.replaceAll("X");
        System.out.println(replacedText); // 输出 abcXdefX
    }
}

正则表达式的高级应用

非贪婪匹配

在正则表达式中,默认的量词是贪婪的,即尽可能多地匹配字符。例如,.* 会匹配从开始到结束的所有字符。但有时候我们希望匹配尽可能少的字符,这就需要使用非贪婪量词。在Java正则表达式中,通过在量词后面加上 ? 可以实现非贪婪匹配。例如,.*? 表示匹配尽可能少的任意字符。考虑下面的代码示例:

import java.util.regex.Matcher;
import java.util.regex.Pattern;

public class NonGreedyMatcher {
    public static void main(String[] args) {
        String text = "<div>content1</div><div>content2</div>";
        String regex = "<div>.*?</div>";
        Pattern pattern = Pattern.compile(regex);
        Matcher matcher = pattern.matcher(text);
        while (matcher.find()) {
            System.out.println(matcher.group());
        }
    }
}

在上述代码中,如果使用贪婪匹配 .*,则会匹配整个字符串 <div>content1</div><div>content2</div>。而使用非贪婪匹配 .*?,则会分别匹配 <div>content1</div><div>content2</div>,这样可以更精确地提取我们想要的内容。

反向引用

反向引用是指在正则表达式中引用之前定义的分组。在Java正则表达式中,通过 \nn 是从1开始的数字)来引用第 n 个分组。例如,要匹配重复的单词,可以使用如下正则表达式:(\w+)\s+\1。这里 (\w+) 是第一个分组,\1 表示引用第一个分组匹配到的内容。\s+ 表示匹配一个或多个空白字符。示例代码如下:

import java.util.regex.Matcher;
import java.util.regex.Pattern;

public class BackreferenceExample {
    public static void main(String[] args) {
        String text = "hello hello world";
        String regex = "(\w+)\s+\1";
        Pattern pattern = Pattern.compile(regex);
        Matcher matcher = pattern.matcher(text);
        if (matcher.find()) {
            System.out.println("找到重复单词: " + matcher.group());
        }
    }
}

在这个例子中,通过反向引用可以找到字符串中重复出现的单词。

零宽断言

零宽断言是一种特殊的匹配方式,它不匹配任何字符,只用于指定位置。零宽断言分为正向先行断言、负向先行断言、正向回顾后发断言和负向回顾后发断言。

  1. 正向先行断言:语法为 X(?=Y),表示 X 后面必须紧跟 Y 才匹配,但不消耗 Y 的字符。例如,abc(?=def) 表示匹配 “abc”,但前提是 “abc” 后面紧跟着 “def”。
  2. 负向先行断言:语法为 X(?!Y),表示 X 后面不能紧跟 Y 才匹配。例如,abc(?!def) 表示匹配 “abc”,但前提是 “abc” 后面不是 “def”。
  3. 正向回顾后发断言:语法为 (?<=Y)X,表示 X 前面必须是 Y 才匹配,但不消耗 Y 的字符。例如,(?<=abc)def 表示匹配 “def”,但前提是 “def” 前面是 “abc”。
  4. 负向回顾后发断言:语法为 (?<!Y)X,表示 X 前面不能是 Y 才匹配。例如,(?<!abc)def 表示匹配 “def”,但前提是 “def” 前面不是 “abc”。

下面是一个使用正向先行断言的示例,用于匹配以 “.txt” 结尾的文件名:

import java.util.regex.Matcher;
import java.util.regex.Pattern;

public class LookaheadExample {
    public static void main(String[] args) {
        String text = "file1.txt file2.doc file3.txt";
        String regex = "\\w+(?=\\.txt)";
        Pattern pattern = Pattern.compile(regex);
        Matcher matcher = pattern.matcher(text);
        while (matcher.find()) {
            System.out.println("找到匹配的文件名: " + matcher.group());
        }
    }
}

在这个例子中,\\w+(?=\\.txt) 表示匹配一个或多个单词字符,并且后面必须紧跟着 “.txt”,这样就可以准确地提取出以 “.txt” 结尾的文件名。

正则表达式在实际项目中的应用场景

数据验证

在Web开发、表单处理等场景中,经常需要对用户输入的数据进行验证。例如,验证邮箱地址的格式是否正确、密码是否符合复杂度要求等。以下是一个验证邮箱地址的示例:

import java.util.regex.Pattern;

public class EmailValidator {
    private static final String EMAIL_PATTERN =
        "^[A - Za - z0 - 9._%+-]+@[A - Za - z0 - 9.-]+\\.[A - Za - z]{2,6}$";

    public static boolean validateEmail(String email) {
        return Pattern.matches(EMAIL_PATTERN, email);
    }

    public static void main(String[] args) {
        String email1 = "test@example.com";
        String email2 = "test.example.com";
        System.out.println(validateEmail(email1)); // 输出 true
        System.out.println(validateEmail(email2)); // 输出 false
    }
}

在上述代码中,通过正则表达式定义了邮箱地址的格式,^$ 确保整个字符串都匹配该模式。[A - Za - z0 - 9._%+-]+ 表示邮箱前缀可以包含字母、数字、下划线、点、百分号、加号和减号,@ 是邮箱地址的分隔符,[A - Za - z0 - 9.-]+ 表示域名部分,\\.[A - Za - z]{2,6} 表示域名后缀,长度为2到6个字母。

文本解析

在处理日志文件、配置文件等文本数据时,经常需要从文本中提取特定的信息。例如,从日志文件中提取时间、用户ID、操作类型等信息。假设日志文件的格式为 “[时间] [用户ID] [操作类型] [操作内容]”,可以使用正则表达式来解析:

import java.util.regex.Matcher;
import java.util.regex.Pattern;

public class LogParser {
    private static final String LOG_PATTERN =
        "^\\[(.*?)\\] \\[(.*?)\\] \\[(.*?)\\] (.*)$";

    public static void parseLog(String log) {
        Pattern pattern = Pattern.compile(LOG_PATTERN);
        Matcher matcher = pattern.matcher(log);
        if (matcher.find()) {
            System.out.println("时间: " + matcher.group(1));
            System.out.println("用户ID: " + matcher.group(2));
            System.out.println("操作类型: " + matcher.group(3));
            System.out.println("操作内容: " + matcher.group(4));
        }
    }

    public static void main(String[] args) {
        String log = "[2023 - 10 - 01 12:00:00] [user123] [login] Successful login";
        parseLog(log);
    }
}

在这个例子中,通过分组将日志中的不同部分提取出来,方便后续的分析和处理。

数据清洗

在数据处理过程中,经常需要对数据进行清洗,去除不需要的字符或格式。例如,从网页源代码中提取纯文本内容,去除HTML标签。可以使用正则表达式来匹配并替换HTML标签:

public class HtmlTagRemover {
    public static String removeHtmlTags(String html) {
        return html.replaceAll("<.*?>", "");
    }

    public static void main(String[] args) {
        String html = "<p>Hello, <b>world</b>!</p>";
        String plainText = removeHtmlTags(html);
        System.out.println(plainText); // 输出 Hello, world!
    }
}

在上述代码中,<.*?> 是一个非贪婪匹配模式,用于匹配任意的HTML标签,通过 replaceAll 方法将其替换为空字符串,从而得到纯文本内容。

综上所述,Java中 String 的正则表达式应用非常广泛,无论是简单的数据验证,还是复杂的文本解析和数据清洗,正则表达式都能发挥重要作用。通过深入理解正则表达式的语法和Java中相关类的使用,开发人员可以更高效地处理字符串相关的任务。在实际应用中,需要根据具体的需求精心设计正则表达式,同时注意性能问题,避免因复杂的正则表达式导致性能瓶颈。