元字符

特殊单字符

. 任意字符,不包括换行

Alt

\d 任意的数字

\D 任意的非数字

\w 任意的字母、数字、下划线

\W 非字母、数字、下划线

\s 空白符号

\S 非空白符号

空白符号

  • \r 回车

  • \n 换行

  • \f 换页

  • \t 制表符

  • \v 垂直制表符

  • \s 任意空白符 \s 能匹配上各种空白符号,也可以匹配上空格。换行有专门的表示方式,在正则中,空格就是用普通的字符英文的空格来表示。

    关于这些空白符号的说明 回车表示,光标移动到行首 换行表示光标移动到下一行,左右位置是和上一行一样的。

范围

  • | 或 ab|bc 表示 ab 或者 gc
  • […],[a-z],[0-9] 括号中的任意字符
  • [^…] 取反,不能是括号中的字符。 管道范围匹配的例子

量词

* 表示 0 到多次

+ 表示 1 到多次

? 表示 0 到一次

{m} 出现 m 次

{m,} 至少出现 m 次

{m,n} 表示 m 到 n 次

断言

单词的边界

使用\b设置单词的边界

行首和行尾

^:匹配字符串的开头 $:匹配字符串的结尾

环视

(?=exp):正向

(?!exp):正向逆

(?<=exp):反向

(?<!exp):反向逆

匹配模式

贪婪模式

尽可能多地匹配字符,正则表达式默认就是贪婪模式的。 @贪婪模式的例子

非贪婪模式

量词后面加上?,找出满足条件的最小的字符串。 @非贪婪模式的例子 @上述例子中的匹配过程

独占模式(possessive)

https://www.regular-expressions.info/possessive.html 避免匹配的时候的回溯,贪婪模式和非贪婪模式都是支持回溯的,例如如下的例子:

匹配规则

大小写不敏感
 1
 2
 3
 4
 5
 6
 7
 8
 9
10
11
12
13
14
15
16
17
18
19
20
    /**
     * 在正则表达式字符串中设置大小写不敏感
     */
    @Test
    public void testMatchInsensitiveInNativeRegString() {
        String string = "cat CAT cat";
        String regString = "(?i)(cat\s?)+";
        assertThat(string.matches(regString)).isEqualTo(true);
    }

    /**
     * 在匹配模式中设置大小写不敏感
     */
    @Test
    public void testMatchInsensitiveInPatternMode() {
        String testString = "cat CAT cat";
        String testReg = "(cat\\s?)+";
        Pattern pattern = Pattern.compile(testReg, Pattern.CASE_INSENSITIVE);
        assertThat(pattern.matcher(testString).matches()).isEqualTo(true);
    }
单行匹配(SingleLine)模式

默认的情况下 . 会匹配所有非换行字符,

可以使用 (?s)启动单行模式,这样 . 会匹配所有的字符。

多行匹配(Multiline)模式

通常情况下 ^匹配字符串的开头,$ 匹配字符串的结尾。 Alt text 加上 (?m) 可以变为多行模式 Alt text

多行模式 ^ 匹配行首 ,$ 匹配行尾。

 1
 2
 3
 4
 5
 6
 7
 8
 9
10
11
12
13
14
15
16
17
    /**
    * Java 中的多行模式
    */
    @Test
    public void matchInMultilineMode() {
        String testString = "Hello ya world\r\nHello ya world";
        String testReg = "^Hello|world$";
        Pattern pattern = Pattern.compile(testReg, Pattern.MULTILINE);
        Matcher matcher = pattern.matcher(testString);
        List<String> results = matcher.results()
                .map(MatchResult::group).toList();
        assertThat(results.size()).isEqualTo(4);
        assertThat(results.get(0)).isEqualTo("Hello");
        assertThat(results.get(1)).isEqualTo("world");
        assertThat(results.get(2)).isEqualTo("Hello");
        assertThat(results.get(3)).isEqualTo("world");
    }
注释模式

正则表达式中也可以写注释 这样的写法有的编程语言式不识别的,一般会提供一个x 模式来识别注释,例如,Java 中就不识别上面的那个正则表达式,可以使用 (?x) 来开启。 多行的情况,正则表达式里面的换行会被忽略

其他模式

除此之外,有的编程语言中(例如 java)中还提供了其他的模式,可在编程的时候查看其使用文档

分组和引用

简单的分组

括号在正则表达式中可以用来分组,被括号括起来的子表达式会被保存为一个分组。 分组会有分组的编号,简单地说第几个括号就是第几个分组。

不保存分组

在括号里面的默认会保存为分组,如果不需要分组只需要在括号里面使用 ?:,不保存分组会提高性能。 例如: 可以看到,上面的例子只保存了一个分组

嵌套的分组

多个括号嵌套的情况下怎么看分组? 看左括号是第几个,就可以确认是第几个分组

命名的分组

分组默认是可以根据序号获得的,分组的编号可能会发生变化,不过也可以为分组指定名字,分组名的格式为?P<groupname>,注意这个不同的语言不一样,其中 java 的是 ?<groupName>

 1
 2
 3
 4
 5
 6
 7
 8
 9
10
11
12
13
14
15
@Test
public void testNamedGroupMatch() {
    String string = "2022-05-03 17:55:00";
    String regString = "(\\d{4}-\\d{2}-\\d{2}) (?<time>\\d{2}:\\d{2}:\\d{2})";
    Pattern pattern = Pattern.compile(regString);
    Matcher matcher = pattern.matcher(string);
    boolean matches = matcher.matches();
    assertThat(matches).isEqualTo(true);
    if (matches) {
       String time = matcher.group("time");
       assertThat(time).isEqualTo("17:55:00");
       String date = matcher.group(1);
       assertThat(date).isEqualTo("2022-05-03");
    }
}

分组引用

1
2
3
4
5
6
7
8
9
 @Test
public void testReplaceByRegGroup() {
    //java分组替换的例子
   String string = "2022-05-03 17:55:00";
   String regString = "((\\d{4})-(\\d{2})-(\\d{2})) ((\\d{2}):(\\d{2}):(\\d{2}))";
   String result = string.replaceAll(regString, "$2年$3月$4日 $6时$7分$8秒");
   assertThat(result).isEqualTo("2022年05月03日 17时55分00秒");
   log.info(result);
}

其他

正则表达式测试的网站

https://regex101.com/r/PnzZ4k/2

https://regexper.com/#a%28%3F%3Abb%29%2Ba