1.为什么使用正则表达式
1.1 什么是正则表达式
正则表达式形如:
个人理解正则表达式是一种规则,用于匹配字符串文本。就是定义一组规则,然后从字符串文本中匹配符合这个规则的内容。
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_]'。 | |
| i | ignore - 不区分大小写 | 将匹配设置为不区分大小写,搜索时不区分大小写: A 和 a 没有区别。 |
| g | global - 全局匹配 | 查找所有的匹配项。 |
| m | multi 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循环内是不可能输出最后一个分组捕获的内容的。