28、Java基础教程之新特性·正则表达式

  • 1️⃣ 概念
  • 2️⃣ 语法
  • 3️⃣ Java 操作API
    • 3.1 匹配
  • 3.2 查找
  • 3.3 替换
  • 3.4 分割
  • 4️⃣ 使用技巧
    • 4.1 预编译正则表达式
  • 4.2 嵌入条件表达式
  • 4.3 使用零宽断言
  • 5️⃣ 应用场景

*

1️⃣ 概念

正则表达式是 一种基于文本的模式匹配方法,它用于搜索、检索和替换一个或多个字符序列。Java 提供了许多工具来支持正则表达式,包括 java.util.regex 包中的类。在本文中,我们将详细讲解 正则表达式的基础语法、Java对于正则操作的API支持以及更多实用操作技巧。

2️⃣ 语法

正则表达式使用特定的语法来定义模式。下面是 Java 正则表达式语法的主要元素:

  • 字符串
    字母、数字和大多数标点符号都可以直接表示为字符串。例如,"abcde" 表示字符串 “abcde”;

  • 字符类
    字符类用于匹配某个字符集合中的任意字符。其中,方括号([])表示字符类的开始和结束。例如,[abcd] 匹配 ‘a’、‘b’、‘c’ 或 ‘d’ 中的任意一个字符。
    字符类还支持范围表示法。例如,[0-9] 表示数字 0 到 9 中的任何一个字符。还可以嵌套字符类,例如 [a-z&&[^bc]] 表示小写字母 a 到 z 中除了 b 和 c 之外的任何字符。

  • [](方括号):定义一个字符集合,在括号内可列出需要匹配的字符,也可使用连字符来表示范围;

  • [^](反向方括号):表示不匹配方括号内的任何字符;

  • [xxx-xxx]:表示匹配一个范围内的任意一个字符。

  • 预定义字符类
    预定义字符类是一组已经定义好的字符类,可以缩短对这些字符的定义。以下是一些常见的预定义字符类:

  • \d: 数字字符。相当于 [0-9]

  • \D: 非数字字符。相当于 [^0-9]

  • \s: 空白字符。包括空格、制表符和换行符;

  • \S: 非空白字符。不包括空格、制表符和换行符;

  • \w: 单词字符。包括数字、字母和下划线。相当于 [a-zA-Z0-9_]

  • \W: 非单词字符。不包括数字、字母和下划线。

  • 量词
    量词用于指定匹配某个模式的次数。以下是一些常见的量词:

  • *(星号): 匹配前面的 0个或多个字符。例如,a*b 可以匹配 ‘b’、‘ab’、‘aab’ 等;

  • +(加号): 匹配前面的 1个或多个字符。例如,a+b 可以匹配 ‘ab’、‘aab’、‘aaab’ 等;

  • ?(问号): 匹配前面的 0个或1个字符。例如,a?b 可以匹配 ‘b’ 或 ‘ab’;

  • {n,m}: 匹配前面的至少 n 个、最多 m 个字符。例如,a{1,3}b 可以匹配 ‘ab’、‘aab’、‘aaab’;

  • {n}: 匹配正好 n 个字符。例如,a{2}b 可以匹配 ‘aab’;

  • {n,}: 匹配前面的至少 n 个字符。例如,a{2,}b 可以匹配 ‘aab’、‘aaab’、‘aaaab’ 等。

量词还可以用于非贪婪模式匹配。在正则表达式中加入 ?,使其尽可能匹配小范围,默认情况下它会尽可能匹配大范围。例如,a.+?b 可以匹配 ‘ab’、‘acb’、‘abbb’ 等。

  • 定位符
    定位符可以指定某个模式的位置。以下是一些常见的定位符:

  • ^: 字符串的开头。例如,^abc 匹配以 ‘abc’ 开头的字符串;

  • $: 字符串的结尾。例如,abc$ 匹配以 ‘abc’ 结尾的字符串;

  • \b:表示匹配单词的边界。例如,\bfoo\b 可以匹配字符串中的 ‘foo’,但不能匹配 ‘foobar’ 或 ‘football’ 等。

  • 特殊字符
    在正则表达式中,一些字符具有特殊含义,需要通过转义字符(\)来表示这些特殊字符。以下是一些常见的特殊字符:

  • .(点号): 匹配除了换行符之外的任何单个字符;

  • |(竖线): 表示逻辑或运算符。例如,a|b 可以匹配 ‘a’ 或 ‘b’;

  • ()(圆括号): 分组运算符。可以将某个子表达式捕获为一个分组,并可以显示该分组的内容。例如,(ab)* 可以匹配零个或多个连续的 ‘ab’ 字符串;

  • ?imnsux: 用于指定正则表达式的特殊标记。例如,(?i) 表示忽略大小写。

3️⃣ Java 操作API

正则Java 基于正则表达式的 API 提供了许多方法来实现各种操作。其中,最常见的操作包括匹配、查找和替换。

Java 类 方法 作用
Pattern boolean matches(String regex, CharSequence input) 字符串匹配,返回一个布尔值表示是否匹配
Pattern compile(String regex) 将给定的正则表达式编译为模式并返回
Matcher matcher(CharSequence input) 在指定的输入序列中查找模式。返回一个匹配器对象,后续可通过该对象的方法来获取有关匹配项的信息
Matcher boolean find() 在目标字符串中搜索与正则表达式匹配的下一个子串。返回一个布尔值表示是否找到了匹配的子串
int start() 获取子串在目标字符串的起始位置
int end() 获取子串在目标字符串的结束位置
String group() 获取子串
String group(int group) 检索匹配得到的某个捕获组的字符串。捕获组是用括号括起来的正则表达式中的子表达式
String replaceFirst(String replacement) 用给定字符串替换目标字符串中与正则表达式匹配的第一个子串,返回字符串
String replaceAll(String replacement) 用给定字符串替换目标字符串中所有与正则表达式匹配的子串,返回字符串
String String[] split(String regex) 将字符串按照正则表达式匹配的模式进行分割,返回一个字符串数组
String replaceAll(String regex, String replacement) 用给定的字符串替换此字符串匹配给定正则表达式的每个子字符串
String replaceFirst(String regex, String replacement) 用给定的字符串替换此字符串匹配给定正则表达式的第一个子字符串

3.1 匹配

Java 的 java.util.regex.Pattern 类提供了 boolean matches(String regex, CharSequence input) 方法来进行字符串匹配操作。该方法匹配整个字符串,并返回一个布尔值表示是否匹配。

以下是一个使用 matches() 方法的例子:

import java.util.regex.*;

public class RegexDemo {
   
     
    public static void main(String[] args) {
   
     
        String pattern = ".*hello.*";
        String input = "hello, world!";
        boolean isMatch = Pattern.matches(pattern, input);
        System.out.println(isMatch);
    }
}

上面的代码将输出:

true

3.2 查找

Java 的 java.util.regex.Matcher 类提供了多个方法来查找字符串中与正则表达式匹配的子串。其中,最常用的方法包括 find()group()

find() 方法在当前目标字符串中搜索与正则表达式匹配的下一个子串,并返回一个布尔值表示是否找到了匹配的子串。如果找到了,则可以使用 start()end() 方法获得该子串的起始和结束位置。可以通过调用 group() 方法来访问该子串。如果 find() 方法已经找到所有匹配的子串,则再次调用会返回 false。

以下是一个示例代码:

import java.util.regex.*;

public class RegexDemo {
   
     
    public static void main(String[] args) {
   
     
        String pattern = "you";
        String input = "How are you? How have you been?";
        
        Pattern p = Pattern.compile(pattern);
        Matcher m = p.matcher(input);

        while (m.find()) {
   
     
            System.out.println("Found match at index " + m.start() + ": " + m.group());
        }
    }
}

上面的代码将输出:

Found match at index 8: you
Found match at index 22: you

如果需要按照某个顺序逐个匹配模式中的多个子串,可以在正则表达式中使用“捕获组”。捕获组用括号图样创建,每个分组都会使用一个数字编号。例如,在以下正则表达式中:To(d{2}),我们创建了一个捕获组并将其编号为 1。这个组将匹配 ‘To’ 后跟两个连续的 ‘d’ 字符序列。

一个Matcher 对象表示一个匹配操作的状态信息。可以通过 find()start()end()group() 方法来访问这些信息。其中,find() 方法用于在目标字符串中查找下一个匹配项;start()end() 方法用于访问上一个匹配项的起始和结束位置;group() 方法用于返回上一个匹配项中指定分组的子串。

3.3 替换

Java 的 java.util.regex.Matcher 类提供了 replaceFirst()replaceAll() 方法来进行替换操作。

replaceFirst() 方法使用给定的替换字符串替换目标字符串中与正则表达式匹配的第一个子串,并返回结果字符串。例如:

import java.util.regex.*;

public class RegexDemo {
   
     
    public static void main(String[] args) {
   
     
        String pattern = "llo";
        String input = "hello, world!";

        Pattern p = Pattern.compile(pattern);
        Matcher m = p.matcher(input);

        String output = m.replaceFirst("foo");
        System.out.println(output);
    }
}

上面的代码将输出:

hefoo, world!

replaceAll() 方法则会使用给定的替换字符串替换目标字符串中所有与正则表达式匹配的子串,并返回结果字符串。例如:

import java.util.regex.*;

public class RegexDemo {
   
     
    public static void main(String[] args) {
   
     
        String pattern = "l";
        String input = "hello, world!";

        Pattern p = Pattern.compile(pattern);
        Matcher m = p.matcher(input);

        String output = m.replaceAll("x");
        System.out.println(output);
    }
}

上面的代码将输出:

hexxo, worxd!

3.4 分割

Java 的 java.lang.String 类提供了 split() 方法,可以将字符串按照正则表达式匹配的模式进行分割,并返回一个字符串数组。

以下是一个示例代码:

import java.util.Arrays;

public class RegexDemo {
   
     
    public static void main(String[] args) {
   
     
        String pattern = "\\s+";
        String input = "apple banana   cherry";
        
        String[] tokens = input.split(pattern);
        System.out.println(Arrays.toString(tokens));
    }
}

上面的代码将输出:

["apple", "banana", "cherry"]

在上面的代码中,我们将字符串 input 按照空白字符(包括空格、制表符和换行符)进行了分割,并将结果存储在了 tokens 数组中。

4️⃣ 使用技巧

除了基本的正则表达式语法和操作之外,还有一些实用的操作技巧可以帮助我们更好地使用 Java 的正则表达式功能。

4.1 预编译正则表达式

如果需要多次使用同一个正则表达式,可以将其预编译为一个 Pattern 对象。这样可以避免每次使用时都重新编译该正则表达式,从而提高效率。

以下是一个使用预编译正则表达式的示例代码:

import java.util.regex.*;

public class RegexDemo {
   
     
    public static void main(String[] args) {
   
     
        String pattern = "\\d+";
        String input = "123-456-7890";

        Pattern p = Pattern.compile(pattern);
        Matcher m = p.matcher(input);

        while (m.find()) {
   
     
            System.out.println(m.group());
        }
    }
}

上面的代码将输出:

123 
456 
7890

4.2 嵌入条件表达式

Java 的正则表达式也支持类似于其他编程语言的三元条件表达式,可以在正则表达式中嵌入条件语句。这通常用于对特定的匹配项应用不同的替换字符串。例如:


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

public class RegexDemo {
   
     
    public static void main(String[] args) {
   
     
        String pattern = "\\[(logged|critical)\\]: (.*?)\\n";
        String input = "[logged]: Hello, world!\n[critical]: Error: Invalid input\n";

        Pattern p = Pattern.compile(pattern);
        Matcher m = p.matcher(input);

        while (m.find()) {
   
     
            System.out.println("string = " + m.group());
            System.out.println("string part 1 = " + m.group(1));
            System.out.println("string part 2 = " + m.group(2));
            String output = m.replaceAll(m.group(1).equals("logged") ? "$2" : "***" );
            System.out.println("replaceAll result = " + output);
        }
    }
}

上面的代码将输出:

string = [logged]: Hello, world!

string part 1 = logged
string part 2 = Hello, world!
replaceAll result = Hello, world!Error: Invalid input

在上面的代码中,我们使用了一个正则表达式来匹配字符串中的特定模式。
整个正则表达式的含义是匹配 “[logged/critical]: (任意字符串,不包括换行符)\n” 这样的文本格式。其中,‘[logged/critical]’ 会被保存到第一个捕获组,‘(任意字符串,不包括换行符)’ 会被保存到第二个捕获组。
然后,使用 replaceAll() 方法并嵌入了一个条件表达式,根据第一个捕获组的值选择替换的字符串。

下面分析下为何是这个结果:在第一次循环时,m.group(1).equals("logged")满足条件于是执行 "$2"分支,表示使用第二个捕获组的字符串来替换所有满足正则的匹配结果,于是替换完后结果就变成了:Hello, world!Error: Invalid input。然后在进行while循环的下一次判定时,此时字符串已经被改变,已经不再有满足正则的子字符串,于是循环结束。

4.3 使用零宽断言

Java 的正则表达式还支持一种特殊的语法,称为“零宽断言”(zero-width assertions)。零宽断言是一种非捕获匹配,用于在匹配某个表达式时指定一个位置,该位置必须满足一定的条件。常用的零宽断言包括正向前瞻断言、负向前瞻断言、正向后顾断言和负向后顾断言。

* 正向前瞻断言(Positive Lookahead Assertion) 是正则表达式中的一种特殊技术,用于在匹配时向前查找确定的模式,但并不消耗被匹配的文本。它使用(?=pattern)来表示,其中pattern表示需要匹配的模式。

正向前瞻断言表示在当前位置,后面必须出现指定的模式。如果指定模式后面的字符与断言中的模式匹配,则断言成功,否则断言失败。在匹配过程中,正向前瞻断言部分不会被包含在最终的匹配结果中。

示例:

  • 对于正则表达式Java(?=\s8),能够匹配字符串"Java 8"中的"Java",只有在"Java"后跟着一个空格和数字8时才匹配成功。
  • 对于正则表达式\w+(?=\d),可以匹配一个或多个字母或数字字符,但只有在其后紧跟一个数字时才匹配成功。

* 负向前瞻断言(Negative Lookahead Assertion) 与正向前瞻断言类似,但是只有在断言部分的模式不匹配时才断言成功。它使用(?!pattern)表示。

示例:

  • 对于正则表达式Java(?! \d),能够匹配字符串"Java SE"中的"Java",只有在"Java"后不跟着一个空格和数字时才匹配成功。
  • 对于正则表达式\w+(?!\d),可以匹配一个或多个字母或数字字符,但只有在其后不跟着数字时才匹配成功。

* 正向后顾断言(Positive Lookbehind Assertion)和负向后顾断言(Negative Lookbehind Assertion) 是来自后面的反向版本。它们通过(?<=pattern)和(?<!pattern)进行表示。

正向后顾断言表示在当前位置,前面必须出现指定的模式。如果指定模式前面的字符与断言中的模式匹配,则断言成功,否则断言失败。

负向后顾断言表示在当前位置,前面不能出现指定的模式。如果指定模式前面的字符与断言中的模式不匹配,则断言成功。

注意,正向后顾断言和负向后顾断言只能匹配固定长度的模式,即不能匹配可变长度的模式。

示例:

  • 对于正则表达式(?`<=@)\w+,可以匹配字符串"myemail@gmail.com"中的"gmail",只有在"@"符号前面紧跟着一个或多个字母、数字或下划线时才匹配成功。
  • 对于正则表达式(?`<!\d+)\w+,可以匹配一个或多个字母、数字或下划线字符,但只有在其前面没有连续的数字时才匹配成功。

正向和负向断言是高级的正则表达式技术,它们能够提供更加灵活的匹配条件,并帮助我们更精确地定位所需的模式。

以下是一个使用正向前瞻断言的示例代码:


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

public class RegexDemo {
   
     
    public static void main(String[] args) {
   
     
        String pattern = "\\w+(?=\\s+is)";
        String input = "John is a man, and Mary is a woman.";

        Pattern p = Pattern.compile(pattern);
        Matcher m = p.matcher(input);

        while (m.find()) {
   
     
            System.out.println(m.group());
        }
    }
}

上面的代码将输出:

John
Mary

在上面的代码中,我们使用了一个正则表达式来查找字符串中的单词,该单词后跟着空白字符和 ‘is’ 字符串。其中,(?=\\s+is) 是一个正向前瞻断言,用于限定匹配项必须后跟空白字符和 ‘is’ 字符串。

5️⃣ 应用场景

正则表达式是用于描述字符串模式的一种语言,具有强大的匹配和搜索能力。它广泛应用于许多计算机程序和系统中,其应用场景有:

  • 文本搜索 / 匹配:可以使用正则表达式搜索或匹配文本中的模式,例如根据特定格式的电话号码、电子邮件地址或域名等;
  • 数据验证 / 校验:可以使用正则表达式验证输入数据是否符合特定格式和规则,例如密码强度、日期格式、身份证号码等;
  • 实时日志文件分析:可以使用正则表达式查找实时生成的日志数据以匹配需要监控的模式,并分析日志文件内容;
  • 编辑器 / IDE 中代码查找替换:可以使用正则表达式在编辑器或 IDE 中进行代码查找与替换功能,提高工作效率。

正则表达式可以跨越许多编程语言和操作系统,主流的编程语言都内置了对正则表达式的支持,例如 Java、Python、Perl、JavaScript、C++、C# 等。总之,正则表达式具备强大的通用性和灵活性,在编写程序和处理文本数据时经常用到。


[* ]nbsp_nbsp 1