ZetCode

Java 正则表达式

最后修改于 2024 年 1 月 27 日

Java 正则表达式教程展示了如何在 Java 中使用正则表达式解析文本。

正则表达式

正则表达式用于文本搜索和更高级的文本操作。正则表达式内置于包括 grep 和 Sed 在内的工具、包括 vi 和 emacs 在内的文本编辑器以及包括 Perl、Java 和 C# 在内的编程语言中。

Java 具有用于使用正则表达式的内置 API;它位于 java.util.regex 中。

正则表达式定义了字符串的搜索模式。Pattern 是正则表达式的编译表示。Matcher 是一个引擎,它解释该模式并针对输入字符串执行匹配操作。Matcher 具有诸如 findmatchesend 等方法来执行匹配操作。当解析正则表达式时出现异常时,Java 会抛出 PatternSyntaxException

Regex 示例

下表显示了一些正则表达式字符串。

Regex 含义
. 匹配任何单个字符。
? 匹配前一个元素一次或不匹配。
+ 匹配前一个元素一次或多次。
* 匹配前一个元素零次或多次。
^ 匹配字符串内的起始位置。
$ 匹配字符串内的结束位置。
| 交替运算符。
[abc] 匹配 a、b 或 c。
[a-c] 范围;匹配 a、b 或 c。
[^abc] 否定;匹配除 a、b 或 c 之外的所有字符。
\s 匹配空白字符。
\w 匹配单词字符;等同于 [a-zA-Z_0-9]

Java 简单正则表达式

在第一个示例中,我们将一个单词与单词列表进行匹配。

com/zetcode/JavaRegexEx.java
package com.zetcode;

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

public class JavaRegexEx {

    public static void main(String[] args) {

        List<String> words = List.of("Seven", "even",
                "Maven", "Amen", "eleven");

        Pattern p = Pattern.compile(".even");

        for (String word: words) {

            Matcher m = p.matcher(word);

            if (m.matches()) {

                System.out.printf("%s matches%n", word);
            } else {

                System.out.printf("%s does not match%n", word);
            }
        }
    }
}

在此示例中,我们在列表中有五个单词。 我们检查哪些单词与 .even 正则表达式匹配。

Pattern p = Pattern.compile(".even");

我们编译该模式。点 (.) 元字符代表文本中的任何单个字符。

for (String word: words) {

    Matcher m = p.matcher(word);

    if (m.matches()) {

        System.out.printf("%s matches%n", word);
    } else {

        System.out.printf("%s does not match%n", word);
    }
}

我们遍历单词列表。使用 matcher 方法创建一个匹配器。如果单词与正则表达式匹配,则 matches 方法返回 true。

Seven matches
even does not match
Maven does not match
Amen does not match
eleven does not match

Java 正则表达式单词边界

元字符 \b 是一个锚点,它匹配一个称为单词边界的位置。它允许搜索整个单词。

com/zetcode/JavaRegexWordBoundaries.java
package com.zetcode;

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

public class JavaRegexWordBoundaries {

    public static void main(String[] args) {

        var text = "This island is beautiful";

        Pattern p = Pattern.compile("\\bis\\b");

        Matcher matcher = p.matcher(text);

        while (matcher.find())
        {
            System.out.printf("%s at %d", matcher.group(), matcher.start());
        }
    }
}

在该示例中,我们查找 is 单词。 我们不想包括 Thisisland 单词。

Pattern p = Pattern.compile("\\bis\\b");

使用两个 \b 元字符,我们搜索 is 整个单词。

while (matcher.find())
{
    System.out.printf("%s at %d", matcher.group(), matcher.start());
}

find 方法尝试查找与模式匹配的输入序列的下一个子序列。group 方法返回先前匹配项匹配的输入子序列。start 方法返回先前匹配项的起始索引。

is at 12

Java 正则表达式隐式单词边界

\w 是用于单词中允许的字符的字符类。对于表示单词的 \w+ 正则表达式,前导和尾随单词边界元字符是隐式的;即 \w+ 等于 \b\w+\b

com/zetcode/JavaRegexImplicitWordBoundaries.java
package com.zetcode;

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

public class JavaRegexImplicitWordBoundaries {

    public static void main(String[] args) {

        var content = """
Foxes are omnivorous mammals belonging to several genera
of the family Canidae. Foxes have a flattened skull, upright triangular ears,
a pointed, slightly upturned snout, and a long bushy tail. Foxes live on every
continent except Antarctica. By far the most common and widespread species of
fox is the red fox.""";

        Pattern p = Pattern.compile("\\w+");

        Matcher matcher = p.matcher(content);

        int count = 0;

        while (matcher.find()) {

            count++;
            System.out.println(matcher.group());
        }

        System.out.printf("There are %d words", count);
    }
}

在该示例中,我们搜索文本中的所有单词。

int count = 0;

while (matcher.find()) {

    count++;
    System.out.println(matcher.group());
}

我们找到所有单词并对其进行计数。

Java 正则表达式货币符号

\p{Sc} 正则表达式可用于查找货币符号。

com/zetcode/JavaRegexCurrency.java
package com.zetcode;

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

public class JavaRegexCurrency {

    public static void main(String[] args) {

        String content = """
Currency symbols: ฿ Thailand bath, ₹ Indian rupee, ₾ Georgian lari, $ Dollar,
€ Euro, ¥ Yen, £ Pound Sterling""";

        String regex = "\\p{Sc}";

        Pattern pattern = Pattern.compile(regex, Pattern.CASE_INSENSITIVE);
        Matcher matcher = pattern.matcher(content);

        while (matcher.find())
        {
            System.out.printf("%s at %d%n", matcher.group(), matcher.start());
        }
    }
}

在该示例中,我们查找货币符号。

String content = """
    Currency symbols: ฿ Thailand bath, ₹ Indian rupee, ₾ Georgian lari, $ Dollar,
    € Euro, ¥ Yen, £ Pound Sterling""";

文本中有几个货币符号。

String regex = "\\p{Sc}";

我们定义货币符号的正则表达式。

while (matcher.find())
{
    System.out.printf("%s at %d%n", matcher.group(), matcher.start());
}

我们找到所有符号及其索引。

฿ at 18
₹ at 35
₾ at 51
$ at 68
€ at 78
¥ at 86
£ at 93

Java 正则表达式锚点

锚点匹配给定文本中字符的位置。在下一个示例中,我们查看一个字符串是否位于句子的开头。

com/zetcode/JavaRegexAnchor.java
package com.zetcode;

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

public class JavaRegexAnchor {

    public static void main(String[] args) {

        List<String> sentences = List.of("I am looking for Jane.",
                "Jane was walking along the river.",
                "Kate and Jane are close friends.");

        Pattern p = Pattern.compile("^Jane");

        for (String word : sentences) {

            Matcher m = p.matcher(word);

            if (m.find()) {
                System.out.printf("%s matches%n", word);
            } else {
                System.out.printf("%s does not match%n", word);
            }
        }
    }
}

我们有三个句子。搜索模式是 ^Jane。该模式检查 "Jane" 字符串是否位于文本的开头。Jane\.$ 将查找句子末尾的 "Jane"。

Java 正则表达式交替

交替运算符 | 使能够创建具有多个选项的正则表达式。

com/zetcode/JavaRegexAlternation.java
package com.zetcode;

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

public class JavaRegexAlternation {

    public static void main(String[] args) {

        List<String> users = List.of("Jane", "Thomas", "Robert",
                "Lucy", "Beky", "John", "Peter", "Andy");

        Pattern p = Pattern.compile("Jane|Beky|Robert");

        for (String user : users) {

            Matcher m = p.matcher(user);

            if (m.matches()) {
                System.out.printf("%s matches%n", user);
            } else {
                System.out.printf("%s does not match%n", user);
            }
        }
    }
}

列表中有九个名字。

Pattern p = Pattern.compile("Jane|Beky|Robert");

这个正则表达式查找 "Jane"、"Beky" 或 "Robert" 字符串。

Java 正则表达式捕获组

圆括号 用于创建捕获组。 这使我们可以将量词应用于整个组,或者将交替选择限制为正则表达式的一部分。

com/zetcode/JavaRegexGroups.java
package com.zetcode;

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

public class JavaRegexGroups {

    public static void main(String[] args) {

        var sites = List.of("webcode.me", "zetcode.com", "freebsd.org",
            "netbsd.org");

        Pattern p = Pattern.compile("(\\w+)\\.(\\w+)");

        for (var site: sites) {

            Matcher matcher = p.matcher(site);

            while (matcher.find()) {

                System.out.println(matcher.group(0));
                System.out.println(matcher.group(1));
                System.out.println(matcher.group(2));
            }

            System.out.println("*****************");
        }
    }
}

在示例中,我们使用组将域名分成两部分。

Pattern p = Pattern.compile("(\\w+)\\.(\\w+)");

我们用括号定义了两个组。

while (matcher.find()) {

    System.out.println(matcher.group(0));
    System.out.println(matcher.group(1));
    System.out.println(matcher.group(2));
}

这些组通过 group 方法访问。group(0) 返回整个匹配的字符串。

webcode.me
webcode
me
*****************
zetcode.com
zetcode
com
*****************
freebsd.org
freebsd
org
*****************
netbsd.org
netbsd
org
*****************

在以下示例中,我们使用组来处理表达式。

com/zetcode/JavaRegexExpressions.java
package com.zetcode;

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

public class JavaRegexExpressions {

    public static void main(String[] args) {

        String[] expressions = {"16 + 11", "12 * 5", "27 / 3", "2 - 8"};
        String pattern = "(\\d+)\\s+([-+*/])\\s+(\\d+)";

        for (var expression : expressions) {

            Pattern p = Pattern.compile(pattern);
            Matcher matcher = p.matcher(expression);

            while (matcher.find()) {

                int val1 = Integer.parseInt(matcher.group(1));
                int val2 = Integer.parseInt(matcher.group(3));
                String oper = matcher.group(2);

                var result = switch (oper) {

                    case "+" -> String.format("%s = %d", expression, val1 + val2);
                    case "-" -> String.format("%s = %d", expression, val1 - val2);
                    case "*" -> String.format("%s = %d", expression, val1 * val2);
                    case "/" -> String.format("%s = %d", expression, val1 / val2);
                    default -> "Unknown operator";
                };

                System.out.println(result);
            }
        }
    }
}

该示例解析四个简单的数学表达式并对其进行计算。

String[] expressions = {"16 + 11", "12 * 5", "27 / 3", "2 - 8"};

我们有一个包含四个表达式的数组。

String pattern = "(\\d+)\\s+([-+*/])\\s+(\\d+)";

在正则表达式模式中,我们有三个组:两个组用于值,一个组用于运算符。

int val1 = Integer.parseInt(matcher.group(1));
int val2 = Integer.parseInt(matcher.group(3));

我们获取值并将其转换为整数。

String oper = matcher.group(2);

我们获取运算符。

var result = switch (oper) {

    case "+" -> String.format("%s = %d", expression, val1 + val2);
    case "-" -> String.format("%s = %d", expression, val1 - val2);
    case "*" -> String.format("%s = %d", expression, val1 * val2);
    case "/" -> String.format("%s = %d", expression, val1 / val2);
    default -> "Unknown operator";
};

使用 switch 表达式,我们计算表达式。

16 + 11 = 27
12 * 5 = 60
27 / 3 = 9
2 - 8 = -6

Java 正则表达式统计词频

在下一个示例中,我们统计文件中单词的频率。

$ wget https://raw.githubusercontent.com/janbodnar/data/main/the-king-james-bible.txt

我们使用《英王钦定本圣经》。

com/zetcode/WordFreqEx.java
package com.zetcode;

import java.io.IOException;
import java.nio.file.Files;
import java.nio.file.Path;
import java.util.Collections;
import java.util.Map;
import java.util.function.Function;
import java.util.regex.MatchResult;
import java.util.regex.Matcher;
import java.util.regex.Pattern;
import java.util.stream.Collectors;

public class WordFreqEx {

    public static void main(String[] args) throws IOException {

        var fileName = "src/resources/the-king-james-bible.txt";
        var text = Files.readString(Path.of(fileName));

        var regex = "[a-zA-Z']+";
        var p = Pattern.compile(regex);
        var matcher = p.matcher(text);

        var words = matcher.results();

        var res = words.collect(Collectors.groupingBy(MatchResult::group,
                Collectors.counting()));

        res.entrySet().stream()
                .sorted(Collections.reverseOrder(Map.Entry.comparingByValue()))
                .limit(10)
                .forEach((e -> System.out.printf("%s %d%n", e.getKey(), e.getValue())));
    }
}

results 方法返回匹配结果的流。我们按单词出现的次数对单词进行分组。我们按频率对它们进行排序并打印前十个单词。

the 62103
and 38848
of 34478
to 13400
And 12846
that 12576
in 12331
shall 9760
he 9665
unto 8942

Java 正则表达式替换字符串

可以使用 replaceAllreplaceFirst 方法替换字符串。这些方法返回修改后的字符串。

com/zetcode/JavaRegexReplacingStrings.java
package com.zetcode;

import java.io.BufferedReader;
import java.io.IOException;
import java.io.InputStreamReader;
import java.net.URL;
import java.nio.charset.StandardCharsets;
import java.util.regex.Matcher;
import java.util.regex.Pattern;
import java.util.stream.Collectors;

public class JavaRegexReplacingStrings {

    public static void main(String[] args) throws IOException {

        URL url = new URL("http://webcode.me");

        try (InputStreamReader isr = new InputStreamReader(url.openStream(),
                StandardCharsets.UTF_8);
             BufferedReader br = new BufferedReader(isr)) {

            String content = br.lines().collect(
                    Collectors.joining(System.lineSeparator()));

            Pattern p = Pattern.compile("<[^>]*>");

            Matcher matcher = p.matcher(content);
            String stripped = matcher.replaceAll("");

            System.out.println(stripped);
        }
    }
}

该示例读取网页的 HTML 数据并使用正则表达式剥离其 HTML 标签。

Pattern p = Pattern.compile("<[^>]*>");

此模式定义了一个匹配 HTML 标签的正则表达式。

String stripped = matcher.replaceAll("");

我们使用 replaceAll 方法删除所有标签。

Java 正则表达式拆分文本

可以使用 Pattern'ssplit 方法拆分文本。

data.csv
22, 1, 3, 4, 5, 17, 18
2, 13, 4, 1, 8, 4
3, 21, 4, 5, 1, 48, 9, 42

我们从 data.csv 文件读取数据。

com/zetcode/JavaRegexSplitText.java
package com.zetcode;

import java.io.IOException;
import java.nio.file.Files;
import java.nio.file.Path;
import java.nio.file.Paths;
import java.util.List;
import java.util.regex.Pattern;

public class JavaRegexSplitText {

    static int sum = 0;

    public static void main(String[] args) throws IOException {

        Path myPath = Paths.get("src/resources/data.csv");

        List<String> lines = Files.readAllLines(myPath);

        String regex = ",";

        Pattern p = Pattern.compile(regex);

        lines.forEach((line) -> {

            String[] parts = p.split(line);

            for (String part : parts) {

                String val = part.trim();

                sum += Integer.parseInt(val);
            }

        });

        System.out.printf("Sum of values: %d", sum);
    }
}

该示例从 CSV 文件读取值并计算它们的总和。它使用正则表达式来读取数据。

List<String> lines = Files.readAllLines(myPath);

一次性地,我们使用 Files.readAllLines 将所有数据读取到字符串列表中。

String regex = ",";

正则表达式是一个逗号字符。

lines.forEach((line) -> {

    String[] parts = p.split(line);

    for (String part : parts) {

        String val = part.trim();

        sum += Integer.parseInt(val);
    }

});

我们遍历这些行,并使用 split 将它们拆分为字符串数组。我们用 trim 截断空格并计算总和值。

Java 不区分大小写的正则表达式

通过设置 Pattern.CASE_INSENSITIVE 标志,我们可以进行不区分大小写的匹配。

com/zetcode/JavaRegexCaseInsensitive.java
package com.zetcode;

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

public class JavaRegexCaseInsensitive {

    public static void main(String[] args) {

        List<String> users = List.of("dog", "Dog", "DOG", "Doggy");

        Pattern p = Pattern.compile("dog", Pattern.CASE_INSENSITIVE);

        users.forEach((user) -> {

            Matcher m = p.matcher(user);

            if (m.matches()) {
                System.out.printf("%s matches%n", user);
            } else {
                System.out.printf("%s does not match%n", user);
            }
        });
    }
}

该示例执行正则表达式的不区分大小写的匹配。

Pattern p = Pattern.compile("dog", Pattern.CASE_INSENSITIVE);

通过将 Pattern.CASE_INSENSITIVE 设置为 Pattern.compile 的第二个参数来设置不区分大小写的匹配。

Java 正则表达式子模式

子模式是模式中的模式。 子模式使用 () 字符创建。

com/zetcode/JavaRegexSubpatterns.java
package com.zetcode;

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

public class JavaRegexSubpatterns {

    public static void main(String[] args) {

        List<String> words = Arrays.asList("book", "bookshelf", "bookworm",
                "bookcase", "bookish", "bookkeeper", "booklet", "bookmark");

        Pattern p = Pattern.compile("book(worm|mark|keeper)?");

        for (String word : words) {

            Matcher m = p.matcher(word);

            if (m.matches()) {
                System.out.printf("%s matches%n", word);
            } else {
                System.out.printf("%s does not match%n", word);
            }
        }
    }
}

该示例创建一个子模式。

Pattern p = Pattern.compile("book(worm|mark|keeper)?");

正则表达式使用一个子模式。 它匹配 bookworm、bookmark、bookkeeper 和 book 单词。

Java 正则表达式电子邮件示例

在下面的示例中,我们创建一个 regex 模式来检查电子邮件地址。

com/zetcode/JavaRegexEmail.java
package com.zetcode;

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

public class JavaRegexEmail {

    public static void main(String[] args) {

        List<String> emails = List.of("luke@gmail.com",
                "andy@yahoocom", "34234sdfa#2345", "f344@gmail.com");

        String regex = "[a-zA-Z0-9._-]+@[a-zA-Z0-9-]+\\.[a-zA-Z.]{2,18}";

        Pattern p = Pattern.compile(regex);

        for (String email : emails) {

            Matcher m = p.matcher(email);

            if (m.matches()) {
                System.out.printf("%s matches%n", email);
            } else {
                System.out.printf("%s does not match%n", email);
            }
        }
    }
}

此示例仅提供一种可能的解决方案。

String regex = "[a-zA-Z0-9._-]+@[a-zA-Z0-9-]+\\.[a-zA-Z.]{2,18}";

电子邮件分为五个部分。第一部分是本地部分。这通常是公司、个人或昵称的名称。[a-zA-Z0-9._-]+ 列出了所有可能的字符,我们可以在本地部分中使用。它们可以使用一次或多次。

第二部分由文字 @ 字符组成。第三部分是域名部分。它通常是电子邮件提供商的域名,例如 yahoo 或 gmail。[a-zA-Z0-9-]+ 是一个字符集,提供了可以在域名中使用的所有字符。+ 量词使用这些字符中的一个或多个。

第四部分是点字符。它前面有转义字符 (\)。这是因为点字符是一个元字符,具有特殊的含义。通过转义它,我们得到一个文字点。

最后一部分是顶级域名:[a-zA-Z.]{2,18}。顶级域名的长度可以从 2 到 18 个字符不等,例如 sk、net、info、travel、cleaning、travelinsurance。最大长度可以是 63 个字符,但如今大多数域名的长度都小于 18 个字符。还有一个点字符。这是因为一些顶级域名有两个部分;例如 co.uk。

来源

包 java.util.regex

在本文中,我们使用了 Java 中的正则表达式。

作者

我叫 Jan Bodnar,我是一位充满激情的程序员,拥有丰富的编程经验。我从 2007 年开始撰写编程文章。到目前为止,我已经撰写了 1,400 多篇文章和 8 本电子书。我拥有超过十年的编程教学经验。

列出所有Java教程