0%

正则表达式

正则表达式

★第二十二章 正则表达式

  • Regular Expression:正则表达式,是对字符串执行模式匹配的技术。

正则表达式快速入门

  • 下面是正则表达式快速入门的例子,对字符串文本中的英文单词进行匹配输出:

    1
    2
    3
    4
    5
    6
    7
    8
    9
    10
    11
    12
    13
    14
    15
    16
    17
    18
    19
    20
    21
    22
    23
    24
    25
    26
    27
    28
    29
    30
    31
    32
    33
    34
    35
    36
    package com.f.chapter22.regexp;


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

    /**
    * @author fzy
    * @date 2023/8/15 19:08
    * 正则表达式快速入门例子
    */
    public class RegExp01 {
    public static void main(String[] args) {
    //要处理的字符串文本
    String content = "1995年,互联网的蓬勃发展给了Oak机会。业界为了使死板、单调的静态网页能够“灵活”起来," +
    "急需一种软件技术来开发一种程序,这种程序可以通过网络传播并且能够跨平台运行。" +
    "于是,世界各大IT企业为此纷纷投入了大量的人力、物力和财力。这个时候,Sun公司想起了那个被搁置起来很久的Oak," +
    "并且重新审视了那个用软件编写的试验平台,由于它是按照嵌入式系统硬件平台体系结构进行编写的,所以非常小," +
    "特别适用于网络上的传输系统,而Oak也是一种精简的语言,程序非常小,适合在网络上传输。" +
    "Sun公司首先推出了可以嵌入网页并且可以随同网页在网络上传输的Applet" +
    "(Applet是一种将小程序嵌入到网页中进行执行的技术),并将Oak更名为Java。" +
    "5月23日,Sun公司在Sun world会议上正式发布Java和HotJava浏览器。" +
    "IBM、Apple、DEC、Adobe、HP、Oracle、Netscape和微软等各大公司都纷纷停止了自己的相关开发项目," +
    "竞相购买了Java使用许可证,并为自己的产品开发了相应的Java平台。";
    //1.先创建一个Pattern对象, 即模式对象, 可以理解为就是一个正则表达式对象, 传入的参数为正则表达式
    Pattern pattern = Pattern.compile("[a-zA-Z]+"); //要匹配英文单词
    //2.创建一个匹配器对象, 传入要处理的字符串
    // matcher 匹配器按照 pattern(模式/样式) 到 content 文本中去匹配。
    Matcher matcher = pattern.matcher(content);
    //3.开始循环匹配
    while (matcher.find()) { //如果找到就返回 true,否则返回 false。
    //匹配到的内容可以通过 matcher.group 得到
    System.out.println("找到: " + matcher.group(0));
    }
    }
    }

★正则表达式底层原理

matcher.find()
  • matcher.find() 完成的任务:

    1. 根据指定的正则表达式,定位满足规则的子字符串。

    2. 找到后,将子字符串 “开始的索引位置” 记录到 matcher 对象的属性 int[] groups 中。

      “开始的索引位置” 记录到 groups[0]

      同时将子字符串 “结束的索引位置 + 1” 记录到 gropus 中。

      “结束的索引位置 + 1” 记录到 groups[1]

    3. 同时记录 oldLast 的值为 “结束的索引位置 + 1”,即下次执行 find 方法时,就从 oldLast 开始继续向下匹配。

matcher.group(0)
  • matcher.group(0) 用来返回匹配到的内容:

    1
    2
    3
    4
    5
    6
    7
    8
    9
    public String group(int group) {
    if (first < 0)
    throw new IllegalStateException("No match found");
    if (group < 0 || group > groupCount())
    throw new IndexOutOfBoundsException("No group " + group);
    if ((groups[group*2] == -1) || (groups[group*2+1] == -1))
    return null;
    return getSubSequence(groups[group * 2], groups[group * 2 + 1]).toString();
    }

    根据 groups[0]groups[1] 记录的位置,从字符文本中截取子字符串 (截取规则为左闭右开的规则) 并返回。

  • 以下面的代码为例:

    1
    2
    3
    4
    5
    6
    7
    8
    9
    10
    11
    12
    13
    14
    15
    16
    17
    18
    19
    20
    21
    22
    23
    24
    25
    26
    27
    28
    29
    30
    31
    32
    33
    34
    35
    36
    37
    38
    39
    40
    41
    42
    43
    44
    45
    46
    47
    48
    49
    50
    51
    52
    53
    54
    55
    56
    57
    58
    59
    60
    package com.f.chapter22.regexp;

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

    /**
    * @author fzy
    * @date 2023/8/15 19:29
    * 分析Java正则表达式的底层实现
    */
    public class RegExpTheory {
    public static void main(String[] args) {
    //给定一段字符串,找出所有四个数字连在一起的子串
    String content = "2000年5月,JDK1.3、JDK1.4和J2SE1.3相继发布,几周后其获得了Apple公司Mac OS X的工业标准的支持。" +
    "2001年9月24日,J2EE1.3发布。2002年2月26日,J2SE1.4发布。自此Java的计算能力有了大幅提升,与J2SE1.3相比," +
    "其多了近62%的类和接口。在这些新特性当中,还提供了广泛的XML支持、安全套接字(Socket)支持(通过SSL与TLS协议)、" +
    "全新的I/OAPI、正则表达式、日志与断言。2004年9月30日,J2SE1.5发布,成为Java语言发展史上的又一里程碑。" +
    "为了表示该版本的重要性,J2SE 1.5更名为Java SE 5.0(内部版本号1.5.0),代号为“Tiger”," +
    "Tiger包含了从1996年发布1.0版本以来的最重大的更新,其中包括泛型支持、基本类型的自动装箱、改进的循环、枚举类型、" +
    "格式化I/O及可变参数。";
    //正则表达式, 匹配四个数字连在一起的子串
    //说明: \\d 表示一个任意的数字
    String regExp = "\\d\\d\\d\\d";
    //1.先创建一个Pattern对象, 即模式对象, 可以理解为就是一个正则表达式对象, 传入的参数为正则表达式
    Pattern pattern = Pattern.compile(regExp);
    //2.创建一个匹配器对象, 传入要处理的字符串
    // matcher 匹配器按照 pattern(模式/样式) 到 content 文本中去匹配。
    Matcher matcher = pattern.matcher(content);
    //3.开始循环匹配
    /*
    * matcher.find() 完成的任务
    * (1)根据指定的正则表达式,定位满足规则的子字符串
    * (2)找到后,将子字符串 “开始的索引位置” 记录到 matcher 对象的属性 int[] groups 中
    * 以第一个找到的 2000 为例,groups[0] = 0 (因为2的索引位置为0)
    * 同时将子字符串 “结束的索引位置 + 1” 记录到 groups 中
    * 以第一个找到的 2000 为例,groups[1] = 4 (因为最后一个0的索引位置为3,3+1=4)
    * (3)同时记录 oldLast 的值为 “结束的索引位置 + 1”,即下次执行 find 方法时,就从 oldLast 开始继续向下匹配
    * */
    while (matcher.find()) { //如果找到就返回 true,否则返回 false。
    //匹配到的内容可以通过 matcher.group 得到
    /**
    * public String group(int group) {
    * if (first < 0)
    * throw new IllegalStateException("No match found");
    * if (group < 0 || group > groupCount())
    * throw new IndexOutOfBoundsException("No group " + group);
    * if ((groups[group*2] == -1) || (groups[group*2+1] == -1))
    * return null;
    * return getSubSequence(groups[group * 2], groups[group * 2 + 1]).toString();
    * }
    * (1)根据 groups[0] 和 groups[1] 记录的位置,从 content 中截取子字符串(截取规则为左闭右开的规则)并返回
    * 以第一个找到的 2000 为例,groups[0] = 0,groups[1] = 4,所以返回的就是 "2000"
    * (2)然后继续向下匹配,将会匹配到 2001,此时会更新 groups[0] 和 groups[1] 的值,即更新后
    * groups[0] = 65,groups[1] = 69,所以返回的就是 "2001"
    * 同样的,oldLast 的值也会进行更新
    */
    System.out.println("找到: " + matcher.group(0));
    }
    }
    }
★matcher.group(i)
  • 正则表达式另外还有分组的概念。将上面的代码改写一下,以下面代码为例:

    1
    2
    3
    4
    5
    6
    7
    8
    9
    10
    11
    12
    13
    14
    15
    16
    17
    18
    19
    20
    21
    22
    23
    24
    25
    26
    27
    28
    29
    30
    31
    32
    33
    34
    35
    36
    37
    38
    39
    40
    41
    42
    43
    44
    45
    46
    47
    48
    49
    50
    51
    52
    53
    54
    55
    56
    57
    58
    59
    60
    61
    62
    63
    64
    65
    66
    67
    68
    69
    package com.f.chapter22.regexp;

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

    /**
    * @author fzy
    * @date 2023/8/15 21:56
    * 分析Java正则表达式的底层实现(引入分组)
    */
    public class RegExpTheory2 {
    public static void main(String[] args) {
    String content = "2000年5月,JDK1.3、JDK1.4和J2SE1.3相继发布,几周后其获得了Apple公司Mac OS X的工业标准的支持。" +
    "2001年9月24日,J2EE1.3发布。2002年2月26日,J2SE1.4发布。自此Java的计算能力有了大幅提升,与J2SE1.3相比," +
    "其多了近62%的类和接口。在这些新特性当中,还提供了广泛的XML支持、安全套接字(Socket)支持(通过SSL与TLS协议)、" +
    "全新的I/OAPI、正则表达式、日志与断言。2004年9月30日,J2SE1.5发布,成为Java语言发展史上的又一里程碑。" +
    "为了表示该版本的重要性,J2SE 1.5更名为Java SE 5.0(内部版本号1.5.0),代号为“Tiger”," +
    "Tiger包含了从1996年发布1.0版本以来的最重大的更新,其中包括泛型支持、基本类型的自动装箱、改进的循环、枚举类型、" +
    "格式化I/O及可变参数。";
    //分组:例如下面的 (\\d\\d)(\\d\\d),正则表达式中有 () 就表示分组,第一个 () 代表第一组,第二个 () 代表第二组
    String regExp = "(\\d\\d)(\\d\\d)";
    //1.先创建一个Pattern对象, 即模式对象, 可以理解为就是一个正则表达式对象, 传入的参数为正则表达式
    Pattern pattern = Pattern.compile(regExp);
    //2.创建一个匹配器对象, 传入要处理的字符串
    // matcher 匹配器按照 pattern(模式/样式) 到 content 文本中去匹配。
    Matcher matcher = pattern.matcher(content);
    //3.开始循环匹配
    /*
    * matcher.find() 完成的任务 (考虑分组)
    * (1)根据指定的正则表达式,定位满足规则的子字符串
    * (2)找到后,将子字符串 “开始的索引位置” 记录到 matcher 对象的属性 int[] groups 中
    * 同时将子字符串 “结束的索引位置 + 1” 记录到 groups 中
    * (2.1)仍以第一个找到的 2000 为例,groups[0] = 0 (因为2的索引位置为0),groups[1] = 4 (因为最后一个0的索引位置为3,3+1=4)
    * 但是因为引入了分组,所以还会将第一组 (对应20)、第二组 (对应00) 的开始和结束索引位置也记录下来
    * (2.2)第一组匹配到的字符串 (20),groups[2] = 0 (因为2的索引位置为0),groups[3] = 2 (因为20的最后一个0的索引位置为1,1+1=2)
    * (2.3)第二组匹配到的字符串 (00),groups[4] = 2 (因为第一个0的索引位置为2),groups[5] = 4 (因为第二个0的索引位置为4,3+1=4)
    * 如果有更多的分组,以此类推...
    * (3)同时记录 oldLast 的值为 “结束的索引位置 + 1”,即下次执行 find 方法时,就从 oldLast 开始继续向下匹配
    * */
    while (matcher.find()) { //如果找到就返回 true,否则返回 false。
    //匹配到的内容可以通过 matcher.group 得到
    /**
    * public String group(int group) {
    * if (first < 0)
    * throw new IllegalStateException("No match found");
    * if (group < 0 || group > groupCount())
    * throw new IndexOutOfBoundsException("No group " + group);
    * if ((groups[group*2] == -1) || (groups[group*2+1] == -1))
    * return null;
    * return getSubSequence(groups[group * 2], groups[group * 2 + 1]).toString();
    * }
    * (1)根据 groups[0] 和 groups[1] 记录的位置,从 content 中截取子字符串(截取规则为左闭右开的规则)并返回
    * 以第一个找到的 2000 为例,groups[0] = 0,groups[1] = 4,所以返回的就是 "2000"
    * 因为引入了分组,所以还可以得到匹配到的子字符串的分组的内容
    * matcher.group(0) 表示得到 groups[0] 和 groups[1] 所对应的子字符串,即表示返回匹配到的子字符串的所有内容。
    * matcher.group(1) 表示得到 groups[2] 和 groups[3] 所对应的子字符串,即表示返回匹配到的子字符串的第 `1` 个分组的内容。
    * matcher.group(2) 表示得到 groups[4] 和 groups[5] 所对应的子字符串,即表示返回匹配到的子字符串的第 `2` 个分组的内容。
    * 以此类推...
    * 从 return getSubSequence(groups[group * 2], groups[group * 2 + 1]).toString(); 也可以看出来
    * (2)然后继续向下匹配,将会匹配到 2001,此时会更新 groups[0] 和 groups[1] 的值,即更新后
    * groups[0] = 65,groups[1] = 69,所以返回的就是 "2001",其分组也和上面类似
    * 同样的,oldLast 的值也会进行更新
    */
    System.out.print("找到匹配内容: " + matcher.group(0) + "\t");
    System.out.print("匹配内容的第一组: " + matcher.group(1) + "\t");
    System.out.println("匹配内容的第二组: " + matcher.group(2));
    }
    }
    }
    • 上面代码注释中比较重要的一句话:但是因为引入了分组,所以还会将第一组 (对应20)、第二组 (对应00) 的开始和结束索引位置也记录下来
    • 从上面的代码可以看出来,matcher.group(i) 表示返回匹配到的子字符串的第 i 个分组的内容
      • matcher.group(0) 表示返回匹配到的子字符串的所有内容。
      • matcher.group(1) 表示返回匹配到的子字符串的第 1 个分组的内容。
      • matcher.group(2) 表示返回匹配到的子字符串的第 2 个分组的内容。
      • ……

★★★正则表达式语法

  • 如果要想灵活的运用正则表达式,必须了解其中各种元字符的功能,元字符从功能上大致分为:
    1. 特殊字符。
    2. 字符匹配符。
    3. 选择匹配符。
    4. 限定符。
    5. 定位符。
    6. 分组组合和反向引用符。
★转义符 \
  • 注意:在我们使用正则表达式去检索某些特殊字符的时候,需要用到转义符号 "\",否则检索不到结果,甚至报错。例如,用 "\(" 匹配左括号而不是 "("
    • 同时,**在 Java 的正则表达式中,用两个 "\\" 代表其他语言的一个 "\"**,所以在 Java 中,用 "\\(" 匹配左括号。
    • 需要用到转义符号的字符有以下:. * + ( ) $ / \ ? [ ] ^ { } | ,当要找到这些字符它们 “本身” 时,需要用到转义符号
  • 注意:在 [] 中匹配特殊字符时,不需要用到转义符号,例如 [.] 表示要匹配的就是 . 字符本身。
★字符匹配符
符号符号示例解释
[]可接收的字符列表[efgh]e、f、g、h中的任意一个字符
[^]不可接收的字符列表[^abc]除a、b、c之外的任意一个字符,包括数字和特殊符号
-连字符A-Z任意单个大写字母
.匹配除 \n 以外的任何字符a..b以a开头,b结尾,中间包括2个任意字符的长度为4的字符串
\\d匹配单个数字字符,相当于[0-9]\\d{3}(\\d)?包含3个或4个数字的字符串
\\D匹配单个非数字字符,相当于[^0-9]\\D(\\d)*以单个非数字字符开头,后接任意个数字字符串
\\w匹配单个数字、大小写字母字符和下划线,相当于[0-9a-zA-Z_]\\d{3}\\w{4}以3个数字字符开头的长度为7的数字字母下划线字符串
\\W匹配单个非数字、大小写字母字符、下划线,相当于[^0-9a-zA-Z_]\\W+\\d{2}以至少1个非数字字母下划线字符开头,2个数字字符结尾的字符串
\\s匹配任何空白字符(空格、制表符等)
\\S匹配任何非空白字符,和 \\s 刚好相反
  • Java 正则表达式默认是区分字母大小写的,可以通过使用 (?i) 的方式实现不区分大小写

    • (?i)abc 表示 abc 都不区分大小写。
    • a(?i)bc 表示 bc 不区分大小写。
    • a((?i)b)c 表示只有 b 不区分大小写。

    也可以通过在创建Pattern 对象时,指定 Pattern.CASE_INSENSITIVE 实现不区分大小写

    • Pattern pattern = Pattern.compile(regExp, Pattern.CASE_INSENSITIVE);
选择匹配符 |
  • 在匹配某个字符串的时候是选择性的,即:既可以匹配这个,又可以匹配那个,这时你需要用到选择匹配符号 |
符号符号示例解释
**``****匹配 `”“` 之前或之后的表达式**
★限定符
  • 用于指定其前面的字符和组合项连续出现多少次。
符号符号示例解释
*指定字符重复0次或n次(无要求)(abc)*仅包含任意个abc的字符串
+指定字符重复1次或n次(至少一次)m+(abc)*以至少1个m开头,后接任意个abc的字符串
?指定字符重复0次或1次(最多一次)m+abc?以至少1个m开头,后接ab或abc的字符串
{n}只能输入n个字符[abcd]{3}由abcd中字母组成的任意长度为3的字符串
{n,}指定至少n个匹配[abcd]{3,}由abcd中字母组成的任意长度不小于3的字符串
{n,m}指定至少n个但不多于m个匹配[abcd]{3,5}由abcd中字母组成的任意长度不小于3,不大于5的字符串
  • 注意:Java 正则表达式匹配默认是贪婪匹配,会尽可能多地匹配。例如:
    • 对于正则表达式 1* 和要匹配的字符串 11111,在匹配时,会优先匹配 11111 而不是其他的,比如 111等。
    • 对于正则表达式 1+ 和要匹配的字符串 11111,在匹配时,会优先匹配 11111 而不是其他的,比如 111等。
    • 对于正则表达式 a1? 和要匹配的字符串 a11111,在匹配时,会优先匹配 a1 而不是 a
    • 对于正则表达式 a{3,5} 和要匹配的字符串 aaaaa,在匹配时,会优先匹配 aaaaa 而不是 aaa 或者 aaaa
定位符
  • 定位符,规定要匹配的字符串出现的位置,比如在字符串的开始还是在结束的位置。
符号符号示例解释
^指定起始字符^[0-9]+[a-z]*以至少1个数字开头,后接任意个小写字母的字符串
$指定结束字符^[0-9]\-[a-z]+$以1个数字开头后接连字符 “-“,并以至少1个小写字母结尾的字符串
\\b匹配目标字符串的边界han\b这里说的字符串的边界指的是子串间有空格的地方,或者是字符串的开始或结束位置
\\B匹配目标字符串的非边界han\B\b 的含义相反
分组
捕获分组
常用分组构造形式说明
(pattern)非命名捕获。捕获匹配的子字符串。编号为零的第一个捕获是由整个正则表达式模式匹配的文本,其它捕获结果则根据左括号的顺序从1开始自动编号。
(?<name>pattern)命名捕获。将匹配的子字符串捕获到一个组名称或编号名称中。用于name的字符串不能包含任何标点符号,并且不能以数字开头。可以使用单引号替代尖括号,例如 (?'name') 代替 (?<name>)
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
package com.f.chapter22.regexp;


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

/**
* @author fzy
* @date 2023/8/17 19:08
* 正则表达式捕获分组例子
*/
public class RegExp02 {
public static void main(String[] args) {
//给定的字符串
String content = "s7788 nn1145java";

/**
* 非命名捕获
* (1)通过 matcher.group(0) 得到匹配到的字符串
* (2)通过 matcher.group(1) 得到匹配到的字符串的第一个分组的内容
* (3)通过 matcher.group(2) 得到匹配到的字符串的第二个分组的内容
* ......
* */
//String regExp = "(\\d\\d)(\\d\\d)";

/**
* 命名捕获
* (1)通过 matcher.group(0) 得到匹配到的字符串
* (2)通过 matcher.group(1) 或 matcher.group("g1") 得到匹配到的字符串的第一个分组的内容
* (3)通过 matcher.group(2) 或 matcher.group("g2") 得到匹配到的字符串的第二个分组的内容
* ......
* */
String regExp = "(?<g1>\\d\\d)(?<g2>\\d\\d)";

//1.先创建一个Pattern对象, 即模式对象, 可以理解为就是一个正则表达式对象, 传入的参数为正则表达式
Pattern pattern = Pattern.compile(regExp);
//2.创建一个匹配器对象, 传入要处理的字符串
// matcher 匹配器按照 pattern(模式/样式) 到 content 文本中去匹配。
Matcher matcher = pattern.matcher(content);
//3.开始循环匹配
while (matcher.find()) { //如果找到就返回 true,否则返回 false。
////非命名捕获
//System.out.println("找到: " + matcher.group(0));
//System.out.println("第一个分组的内容: " + matcher.group(1));
//System.out.println("第二个分组的内容: " + matcher.group(2));

//命名捕获(既可以用上面非命名捕获的方法,也可以用自己独有的方法)
System.out.println("找到: " + matcher.group(0));
System.out.println("第一个分组的内容(编号获取): " + matcher.group(1));
System.out.println("第一个分组的内容(命名获取): " + matcher.group("g1"));
System.out.println("第二个分组的内容(编号获取): " + matcher.group(2));
System.out.println("第二个分组的内容(命名获取): " + matcher.group("g2"));
}
}
}
非捕获分组
常用分组构造形式说明
(?:pattern)匹配 pattern 但不捕获该匹配的子表达式,即它是一个非捕获匹配,不存储供以后使用的匹配。这**对于用 "or" 字符 (`”
(?=pattern)它是一个非捕获匹配。例如,`’Windows (?=95
(?!pattern)该表达式匹配不处于匹配 pattern 的字符串的起始点的搜索字符串。它是一个非捕获匹配。例如,`’Windows (?!95
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
package com.f.chapter22.regexp;

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

/**
* @author fzy
* @date 2023/8/17 19:19
* 正则表达式非捕获分组例子
*/
public class RegExp03 {
public static void main(String[] args) {
//给定的字符串
String content = "hello小白狗 world小白猫 小白鸽! 小白";

/**
* 给定的非捕获正则表达式(?:pattern)
* 匹配 `pattern` 但不捕获该匹配的子表达式,即它是一个非捕获匹配,不存储供以后使用的匹配。
* 这对于用 `"or"` 字符 (`"|"`) 组合模式部件的情况很有用。
* 例如,`'industr(?:y|ies)'` 是比 `'industry|industries'` 更经济的表达式。
*/
//传统的方式: String regExp = "小白狗|小白猫|小白鸽";
//String regExp = "小白(?:狗|猫|鸽)"; //String regExp = "小白(狗|猫|鸽)"; 好像也行

/**
* 给定的非捕获正则表达式(?=pattern)
* 它是一个非捕获匹配。例如,`'Windows (?=95|98|NT|2000)'` 匹配 `"Windows 2000"` 中的 `"Windows"`,
* 但不匹配 `"Windows 3.1"` 中的 `"Windows"`。
*/
//找到“小白”关键字,但是只匹配“小白狗”或者“小白猫”中的“小白”
//String regExp = "小白(?=狗|猫)";

/**
* 给定的非捕获正则表达式(?!pattern)
* 该表达式匹配不处于匹配 `pattern` 的字符串的起始点的搜索字符串。它是一个非捕获匹配。
* 例如,`'Windows (?!95|98|NT|2000)'` 匹配 `"Windows 3.1"` 中的 `"Windows"`,
* 但不匹配 `"Windows 2000"` 中的 `"Windows"`。
*/
//找到“小白”关键字,但是不匹配“小白狗”或者“小白猫”中的“小白”,匹配其他小白
String regExp = "小白(?!狗|猫)";

//1.先创建一个Pattern对象, 即模式对象, 可以理解为就是一个正则表达式对象, 传入的参数为正则表达式
Pattern pattern = Pattern.compile(regExp);
//2.创建一个匹配器对象, 传入要处理的字符串
// matcher 匹配器按照 pattern(模式/样式) 到 content 文本中去匹配。
Matcher matcher = pattern.matcher(content);
//3.开始循环匹配
while (matcher.find()) { //如果找到就返回 true,否则返回 false。
System.out.println("找到: " + matcher.group(0));
}
}
}
★反向引用
  • 反向引用和分组、捕获之间是有关系的,所以下面再重述一次分组和捕获的概念:

    • 分组:我们可以用圆括号组成一个比较复杂的匹配模式,那么一个圆括号的部分我们可以看作是一个子表达式 / 一个分组。
    • 捕获:把正则表达式中的子表达式 / 分组匹配的内容,保存到内存中以数字编号或显式命名的组里,方便后面引用,从左向右,以分组的左括号为标志,第一个出现的分组的组号为 1 ,第二个为 2 ,以此类推。组 0 代表的是整个正则式。
    • 反向引用圆括号的内容被捕获后,可以在这个括号后被使用,从而写出一个比较实用的匹配模式,这个我们称为反向引用,这种引用既可以是在正则表达式内部,也可以是在正则表达式外部,**内部反向引用 \\分组号,外部反向引用 $分组号**。
  • 反向引用的例子:

    1
    2
    3
    4
    5
    6
    7
    8
    9
    10
    11
    12
    13
    14
    15
    16
    17
    18
    19
    20
    21
    22
    23
    package com.f.chapter22.regexp;

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

    /**
    * @author fzy
    * @date 2023/8/18 11:59
    * 反向引用例子
    */
    public class RegExp04 {
    public static void main(String[] args) {
    String content = "hello, world123443222221!";
    //String regExp = "(\\d)\\1"; //要匹配两个连续的相同数字
    //String regExp = "(\\d)\\1{4}"; //要匹配五个连续的相同数字
    String regExp = "(\\d)(\\d)\\2\\1"; //要匹配个位与千位相同、十位与百位相同的数字
    Pattern pattern = Pattern.compile(regExp);
    Matcher matcher = pattern.matcher(content);
    while (matcher.find()) {
    System.out.println("找到: " + matcher.group(0));
    }
    }
    }
★非贪婪匹配 ?
  • 此字符 ? 紧随任何其他限定符(*、+、?、{n}、{n,}、{n,m})之后时,匹配模式是 “非贪心的”。**”非贪心的” 模式匹配搜索到尽可能短的匹配的字符串,而默认的 “贪心的” 模式匹配搜索到尽可能长的匹配字符串。**例如,在字符串 "oooo" 中,"o+?" 只匹配单个 "o",而 "o+" 匹配所有 "o"

正则表达式常用类

Pattern
  • pattern 对象是一个正则表达式对象。Pattern 类没有公共构造方法。要创建一个 Pattern 对象需要调用其公共静态方法 compile,它返回一个 Pattern 对象。该方法接受一个正则表达式作为它的第一个参数。

  • Pattern 的常用方法如下代码所示:

    1
    2
    3
    4
    5
    6
    7
    8
    9
    10
    11
    12
    13
    14
    15
    16
    17
    18
    19
    20
    21
    22
    23
    24
    25
    26
    27
    28
    29
    30
    package com.f.chapter22.pattern;

    import org.junit.jupiter.api.Test;

    import java.util.regex.Pattern;

    /**
    * @author fzy
    * @date 2023/8/18 10:45
    * Pattern类的常用方法演示
    */
    public class PatternMethod {
    @Test
    //1.matches方法,用于整体匹配,在验证输入的字符串是否满足条件时使用,
    // 满足时返回true,否则返回false
    /**
    * public static boolean matches(String regex, CharSequence input) {
    * Pattern p = Pattern.compile(regex);
    * Matcher m = p.matcher(input);
    * return m.matches();
    * }
    * 底层调用的是 Matcher 类的 matches 方法
    */
    public void patternMatches() {
    String content = "hello, world!";
    String regExp = "hello.*";
    boolean matches = Pattern.matches(regExp, content);
    System.out.println("整体匹配为: " + matches);
    }
    }
Matcher
  • Matcher 对象是对输入字符串进行解释和匹配的引擎。与 Pattern 类一样,Matcher 也没有公共构造方法。你需要调用 Pattern 对象的 matcher 方法来获得一个 Matcher 对象

  • Matcher 类的常用方法如下所示:

    方法及说明
    public int start():返回以前匹配的初始索引。
    public int start(int group):返回在以前的匹配操作期间,由给定组所捕获的子序列的初始索引。
    public int end():返回最后匹配字符之后的偏移量。
    public int end(int group):返回在以前的匹配操作期间,由给定组所捕获子序列的最后字符之后的偏移量。
    public boolean lookingAt():尝试将从区域开头开始的输入序列与该模式匹配。
    public boolean find():尝试查找与该模式匹配的输入序列的下一个子序列。
    public boolean find(int start):重置此匹配器,然后尝试查找匹配该模式、从指定索引开始的输入序列的下一个子序列。
    public boolean matches():尝试将整个区域与模式匹配。
    public String replaceAll(String replacement):将与给定模式相匹配的输入序列的每个子序列替换为给定的字符串。方法返回的才是替换后的结果。
    1
    2
    3
    4
    5
    6
    7
    8
    9
    10
    11
    12
    13
    14
    15
    16
    17
    18
    19
    20
    21
    22
    23
    24
    25
    26
    27
    28
    29
    30
    31
    32
    33
    34
    35
    36
    37
    package com.f.chapter22.matcher;

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

    /**
    * @author fzy
    * @date 2023/8/18 11:00
    * Matcher类的常用方法演示
    */
    public class MatcherMethod {
    public static void main(String[] args) {
    //要匹配的内容
    String content = "hello, world! hello smith";
    //指定的正则表达式
    String regExp = "hello";
    //1.创建pattern对象
    Pattern pattern = Pattern.compile(regExp);
    //2.创建matcher对象
    Matcher matcher = pattern.matcher(content);
    //3.得到匹配结果
    while (matcher.find()) {
    System.out.println("============");
    //(1)`public int start()`:返回以前匹配的初始索引。
    System.out.println("匹配的内容的开始索引 = " + matcher.start());
    //(2)`public int end()`:返回最后匹配字符之后的偏移量。
    System.out.println("匹配的内容的结束索引 + 1 = " + matcher.end());
    }
    System.out.println();

    //(3)`public String replaceAll(String replacement)`:将与给定模式相匹配的输入序列的每个子序列替换为给定的字符串。方法返回的才是替换后的结果。
    // 将上面内容中的 hello 替换为 hi
    String newContent = matcher.replaceAll("hi");
    System.out.println(content); //hello, world! hello smith
    System.out.println(newContent); //hi, world! hi smith
    }
    }

String类中使用正则表达式

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
package com.f.chapter22.regexp;

/**
* @author fzy
* @date 2023/8/18 15:28
* String类中使用正则表达式
* 替换、分割、匹配
*/
public class StringReg {
public static void main(String[] args) {
//1.替换
String content = "2000年5月,JDK1.3、JDK1.4和J2SE1.3相继发布,几周后其获得了Apple公司Mac OS X的工业标准的支持。";
//使用正则表达式将 JDK1.3、JDK1.4 替换为 JDK
String regExp = "JDK1\\.[34]";
String newContent = content.replaceAll(regExp, "JDK");
System.out.println(newContent);

//2.分割
content = "hello#abc-jack12smith~北京";
//使用正则表达式按照 #、-、~、数字 来进行分割
regExp = "#|-|~|\\d+";
String[] split = content.split(regExp);
for (String substring : split) {
System.out.println("分割: " + substring);
}

//3.匹配
String phone = "13988888888";
//使用正则表达式判断手机号是否为 138、139 开头
regExp = "13[89]\\d{8}";
System.out.println("手机号是否为 138、139 开头: " + phone.matches(regExp));
}
}
---------------The End---------------