浅入浅出正则表达式

99 阅读3分钟

1.为什么使用正则表达式

1.1 什么是正则表达式

正则表达式形如:
1671279568(1).jpg

个人理解正则表达式是一种规则,用于匹配字符串文本。就是定义一组规则,然后从字符串文本中匹配符合这个规则的内容。

1.2 为啥要用

前几天刷到一道算法题的时候,思考了各种边界情况,各种小的节点解决方案,前前后后写了八十几行代码。然而答案题解只用了十行左右的代码。原因就是使用了正则表达式,所以不会正则表达式的人会有多吃亏。所以学会正则表达式是一件可以提升我们编码效率的事情。

2.常用的正则表达式描述符

字符描述
\转义字符
^匹配位置,表示字符串的开头
$匹配位置,表示字符串的结尾
*表示前面子表达式出现的次数,* 表示0次或多次
+表示前面子表达式出现的次数,+ 表示1次或多次
?表示前面子表达式出现0次或一次
{n}n 是一个非负整数。匹配确定的 n 次。例如,'o{2}' 不能匹配 "Bob" 中的 'o',但是能匹配 "food" 中的两个 o。
{n,}n 是一个非负整数。至少匹配n 次。例如,'o{2,}' 不能匹配 "Bob" 中的 'o',但能匹配 "foooood" 中的所有 o。'o{1,}' 等价于 'o+'。'o{0,}' 则等价于 'o*'。
{n,m}m 和 n 均为非负整数,其中n <= m。最少匹配 n 次且最多匹配 m 次。例如,"o{1,3}" 将匹配 "fooooood" 中的前三个 o。'o{0,1}' 等价于 'o?'。请注意在逗号和两个数之间不能有空格。
?当该字符紧跟在任何一个其他限制符 (*, +, ?, {n}, {n,}, {n,m}) 后面时,匹配模式是非贪婪的。非贪婪模式尽可能少的匹配所搜索的字符串,而默认的贪婪模式则尽可能多的匹配所搜索的字符串。例如,对于字符串 "oooo",'o+?' 将匹配单个 "o",而 'o+' 将匹配所有 'o'。
.匹配除换行符(\n、\r)之外的任何单个字符。要匹配包括 '\n' 在内的任何字符,请使用像" (.|\n) " 的模式。
x|y匹配 x 或 y。例如,'z|food' 能匹配 "z" 或 "food"。'(z|f)ood' 则匹配 "zood" 或 "food"。
[xyz]字符集合。匹配所包含的任意一个字符。例如, '[abc]' 可以匹配 "plain" 中的 'a'。
[^xyz]负值字符集合。匹配未包含的任意字符。例如, '[^abc]' 可以匹配 "plain" 中的'p'、'l'、'i'、'n'。
[a-z]字符范围。匹配指定范围内的任意字符。例如,'[a-z]' 可以匹配 'a' 到 'z' 范围内的任意小写字母字符。
[^a-z]负值字符范围。匹配任何不在指定范围内的任意字符。例如,'[^a-z]' 可以匹配任何不在 'a' 到 'z' 范围内的任意字符。
\b匹配一个单词边界,也就是指单词和空格间的位置。例如, 'er\b' 可以匹配"never" 中的 'er',但不能匹配 "verb" 中的 'er'。
\B匹配非单词边界。'er\B' 能匹配 "verb" 中的 'er',但不能匹配 "never" 中的 'er'。
\w匹配字母、数字、下划线。等价于'[A-Za-z0-9_]'。
\W匹配非字母、数字、下划线。等价于 '[^A-Za-z0-9_]'。
iignore - 不区分大小写将匹配设置为不区分大小写,搜索时不区分大小写: A 和 a 没有区别。
gglobal - 全局匹配查找所有的匹配项。
mmulti line - 多行匹配使边界字符 ^ 和 $ 匹配每一行的开头和结尾,记住是多行,而不是整个字符串的开头和结尾。

3.正则表达式进阶

3.1 分组捕获

如果我们需要重复某个字符,只需要在字符后面加上限定符,* + ? 。但是如果我们想重复某些字符呢?这个时候就可以使用分组()。例如:(abc)+ 。括号内的字符可以看成是一个整体。

捕获组

分组这个概念下又分捕获组非捕获组捕获组就是正则表达式除了匹配符合规则的文本之外,还将每个分组匹配到的文本都存储到内存中。后续可以通过反向引用获取这些捕获的内容。

如何区分各个捕获组?举个例子:(A)(B(C)),捕获组可以通过从左到右计算其开括号来编号。

编号
0   (A)(B(C))
1   (A)
2   (B(C))
3   (C)

分组0代表整个正则表达式,那么如何进行反向引用?
引用分组1:\1
引用分组2:\2

非捕获组

那么什么是非捕获组?非捕获组就是仅仅对内容进行匹配,并不会将匹配的内容进行捕获。也就是不会将匹配的内容存储到内存中。后续地反向引用不会引用到非捕获组所匹配的内容。自然,非捕获组参与捕获组的统计。

例如:(A)(B(?:C))

编号
0   (A)(B(C))
1   (A)
2   (B(C))

这个时候(?:C)就是非捕获组。

3.2 零宽断言

在使用正则表达式时,有时我们需要捕获的内容前后必须是特定内容,但又不捕获这些特定内容的时候,零宽断言就起到作用了。

零宽断言有四种:

断言描述
(?=X )   正向先行断言,X为特定内容,匹配的内容右边X才会被匹配
(?!X )   负向先行断言,X为特定内容,匹配的内容右边不是X才会被匹配
(?<=X )   正向后发断言,X为特定内容,匹配的内容X才会被匹配
(?<!X )   负向后发断言,X为特定内容,匹配的内容左边不是X才会被匹配

举个例子:
(?=X ) :
文本:1st 2nd 3pc
正则表达式:\d(?=nd)
结果:2

(?!X ):
文本:1st 2nd 3pc
正则表达式:\d(?!nd)
结果:1 3

(?<=X ):
文本:#1 $2 %3
正则表达式:(?<=%)\d
结果:3

(? 文本:#1 $2 %3
正则表达式:(?<!%)\d
结果:1 2

4.Java中使用正则表达式

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

public static void main(String[] args) {
    /*设置正则表达式*/
    Pattern p = Pattern.compile("[a-z]{3}");
    /*设置需要匹配的文本*/
    Matcher m = p.matcher("acb");
    System.out.println(m.matches());
}

Java使用正则表达式分组:

public static void main(String[] args) {
    /*设置正则表达式*/
    Pattern p = Pattern.compile("(\d\w)*(\d-)*");
    /*设置需要匹配的文本*/
    Matcher m = p.matcher("4d3f4-");
    if(m.matches()){
        System.out.println(m.groupCount());
        for(int index=0;index<m.groupCount();index++){
            System.out.println(m.group(index));
        }
        System.out.println(m.group(2));
    }
}

输出结果:

2
4d3f4-
3f
4-

在输出分组数量的时候,输出了2。如果算上分组0,分组1,分组2应该有三个分组。m.groupCount()应该没有将分组0进行数量的统计,但实际上是存在的。这里需要注意。如果index初始为0,那么for循环内是不可能输出最后一个分组捕获的内容的。

参考

1.正则表达式 - 教程
2.正则表达式练习网站
3.正则表达式高级用法(分组与捕获)