1、正则表达式
正则表达式是对字符串【包括普通字符(例如,a 到 z 之间的字母)和特殊字符(称为“元字符”)】操作的一种逻辑公式,就是用事先定义好的一些特定字符、及这些特定字符的组合,组成一个“规则字符串”,这个“规则字符串”用来表达对字符串的一种过滤逻辑。正则表达式是一种文本模式,该模式描述在搜索文本时要匹配的一个或多个字符串。
2、正则表达式语法
2.1、普通字符
字母、数字、汉字、下划线以及没有特殊定义的标点符号,都是普通字符。表达式中的普通字符在匹配一个字符串的时候,匹配与之相同的字符。什么字符串就可以匹配目标字符串中的相同字符例如:1
2
3
4“abc”可以匹配到abcdefs中的abc字符串
“aaa”可以匹配到ahaaa中的aaa字符串
“_”可以匹配到hello_world_中的两个_字符,
“,”在hello world中找不到匹配
2.2、简单的转义字符
除了一些常见的可见字符和能直接表达的字符外,还有一些特殊的字符,例如:制表符、换行符等。其中\
有特殊用途,用于表达转义,例如:\n
代表换行符号、\t
代表制表符号,因此,我们如果想匹配字面意义的符号\
时,也需要转义,也就是\\
才能表示字符\
。
【注意,在java语言中, \
也是转义字符,在java字符串中,想表达字面意义的\
符号时,也是用同样的方式,也就是:\\
表示一个字面意义的\
符号,而如果在java中定义字符串String regex = "\\"
时,对于正则表达式来说就是一个\
,在正则表达式中由于\
也是转义字符,因此,如果我们想用正则表达式匹配字面字符\
时,在java的String定义中还应该加一个\
,也就是再加一个\\
。因此,如果想要匹配字面意义的符号\
时,最终在java中定义应该为:String regex = "\\\\"
】
常用的需要转义的字符归纳如下:
表达式 | 真实字符 | |
---|---|---|
\n |
换行符 | |
\t |
制表符 | |
\^ |
^ | |
\$ |
$ | |
\( |
( | |
\) |
) | |
\{ |
{ | |
\} |
} | |
\? |
? | |
\+ |
+ | |
\* |
* | |
`\ | ` | |
\[ |
[ | |
\] |
] | |
\. |
. |
2.3、标准字符集合(大小写区分)
表达式 | 说明 | 举例(罗列出满足匹配条件的字符) |
---|---|---|
\d |
匹配任意一位数字,0~9的任意一个,一个\d匹配一个字符。 | \d 匹配 abc3a 中的3;匹配 ab34f 中的3和4 |
\D |
大写的D表示\d之外的集合,一个\D只匹配一个字符 | \D 匹配 abc3a 中的a、b、c和a;匹配 ab34f 中的a、b和f |
\w |
任意一个字母或数字或下划线,也就是A~Z,a~z,0~9,_ 中任意一个 | ab c a 中的a、b、c和a |
\W |
\w之外的集合,一个\W匹配一个字符 | ab c a中的b后面和c后面的两个空格 |
\s |
包括空格、制表符、换行符等空白字符中的任意一个,一个\s匹配一个字符 | ab c a中的b后面和c后面的两个空格 |
\S |
\s之外的集合,一个\S匹配一个字符 | 举例略 |
. |
匹配任意一个字符,但是\n除外,如果要匹配包括\n在内的所有字符,使用[\s\S] | 举例略 |
2.4、自定义字符集合
[ ] 方括号匹配方法,可以匹配方括号中任意一个字符,除了^ \ 和 - 外,所有的特殊字符都不用在[ ]中转义。^在方括号中表示取补集;\为转义标识符;-在方括号中表示范围符号。
举例:1
2
3
4[ab5@] 匹配 a b 5或@字符
[^abc] 匹配 a b c 之外的任意一个字符
[f-k] 匹配 f 到 k 之间的任意一个字符
[^A-F0-3] 匹配 A 到 F 和 0 到 3 之外的任意一个字符
2.5、量词
量词用来规定某一字符或某一类字符出现的次数范围。
表达式 | 说明 | 举例 |
---|---|---|
{n} | 匹配n次 | \d{6}表示匹配6位整数 |
{n,} | 至少匹配n次 | \d{6,}表示匹配至少6位整数 |
{n,m} | 最少匹配n次,最多匹配m次 | \d{2,6}表示匹配整数位数2到6之间的整数 |
? |
匹配0次或1次 | \d匹配0位或1位整数 |
* | 匹配0次或多次 | 举例略 |
+ | 匹配1次或多次 | 举例略 |
默认为贪婪模式(匹配字符越多越好,默认!)
非贪婪模式为匹配字符越少越好,在匹配特殊符号后加上?表示非贪婪模式。
例如:
1 | \d{1,5} 匹配字符 123456789 时会匹配出12345 或6789 【贪婪模式】 |
2.6、 字符边界
字符边界不匹配任何一个字符,他是匹配的一种位置。我们成为零宽字符,有:
表达式 | 说明 |
---|---|
^ | 与字符串开始的地方匹配 |
$ | 与字符串结束的地方匹配 |
\b | 匹配一个单词边界 |
2.7、选择符和分组
表达式 | 说明 |
---|---|
| | 左右两边表达式之间 或 关系,匹配左边或者右边 |
() | 1、在被修饰匹配次数的时候,括号中的表达式可以作为整体被修饰 2、去匹配结果的时候,括号中的表达式匹配到的内容可以被单独得到 3、没对括号会分配一个编号,使用()的捕获根据左括号的顺序从1开始自动编号 |
(?:表达式) | 一些表达式中,不得不使用( ),但又不需要保存( )中子表达式匹配的内容,这时可以用非捕获组来抵消使用()带来的副作用 |
\nnn | 对每一对()编号进行引用 |
2.8、预搜索(零宽断言)
零宽断言的意思是(匹配宽度为零,满足一定的条件/断言)。
零宽断言用于查找在某些内容(但并不包括这些内容)之前或之后的东西,也就是说它们像 \b ^ $ \< > 这样的锚定作用,仅仅用于指定一个位置,不参与内容匹配,这个位置应该满足一定的条件(即断言),因此它们也被称为零宽断言。 断言用来声明一个应该为真的事实。正则表达式中只有当断言为真时才会继续进行匹配。
其中零宽断言分四种:
表达式 | 说明 |
---|---|
(?=exp) | 断言自身出现的位置的后面能匹配表达式exp |
(?<=exp) | 断言自身出现的位置的前面能匹配表达式exp |
(?!exp) | 断言此位置的后面不能匹配表达式exp |
(?<!exp) | 断言此位置的前面不能匹配表达式exp |
(?=表达式) 零宽度正预测先行断言 表示匹配表达式前面的位置
例如:
要匹配 cooking ,singing ,doing中除了ing之外的内容,只取cook, sing, do的内容,这时候的正则表达式可以用 [a-z]*(?=ing) 来匹配
注意:先行断言的执行步骤是这样的先从要匹配的字符串中的最右端找到第一个 ing (也就是先行断言中的表达式)然后 再匹配其前面的表达式,若无法匹配则继续查找第二个 ing 再匹配第二个 ing 前面的字符串,若能匹配则匹配,符合正则的贪婪性。
例如:
.*(?=ing) 可以匹配 “cooking singing” 中的 “cooking sing” 而不是 cook
(?<=表达式) 零宽度正回顾后发断言 表示匹配表达式后面的位置
例如 (?<=abc).* 可以匹配 abcdefg 中的 defg
注意:后发断言跟先行断言恰恰相反 它的执行步骤是这样的:先从要匹配的字符串中的最左端找到第一个abc(也就是先行断言中的表达式)然后 再匹配其后面的表达式,若无法匹配则继续查找第二个 abc 再匹配第二个 abc 后面的字符串,若能匹配则匹配。
例如 (?<=abc).* 可以匹配 abcdefgabc 中的 defgabc 而不是 abcdefg
(?!表达式) 负向零宽先行断言
负向零宽断言 (?!exp) 也是匹配一个零宽度的位置,不过这个位置的“断言”取表达式的反值,例如 (?!exp) 表示 “exp” 前面的位置,如果 “exp” 不成立 ,匹配这个位置;如果 “exp” 成立,则不匹配。
同样,负向零宽断言也有“先行”和“后发”两种
负向零宽先行断言为 (?!exp)
负向零宽后发断言为 (?<!exp)
(?﹤!表达式) 负向零宽后发断言
负向零宽断言要注意的跟正向的一样。
3、实战
3.1、匹配不包含属性的HTML标签里的内容
(?<=<(\w+)>).*(?=<\/\1>)
匹配不包含属性的简单HTML标签内里的内容。
说明:(<?(\w+)>)
指定了这样的前缀:被尖括号括起来的单词(比如可能是<b>
),然后是.*(任意的字符串)
,最后是一个后缀(?=<\/\1>)
。注意后缀里的\/
,它用到了前面提过的字符转义;\1
则是一个反向引用,引用的正是捕获的第一组,前面的(\w+)
匹配的内容,这样如果前缀实际上是<b>
的话,后缀就是</b>
了。整个表达式匹配的是<b>
和</b>
之间的内容(再次提醒,不包括前缀和后缀本身)。