前言
在编程语言中,除了容器集合、多线程并发、IO流等等基础组件之外,还有一项专门处理字符串的“奇淫巧技”,这就是正则表达式。Perl语言正是凭借强大的正则表达式才异军突起的。
对于Java来说,自从jdk1.7之后,正则表达式也逐渐强大起来,不再是之前的半吊子了。现在的Java也非吴下阿蒙,其正则表达式也不逊于其他主流语言了。
本篇将详细描述Java正则表达式的用法、技巧等等,让你领略一下正则表达式的强大功能。
由于内容较多,整个文章分为上中下三篇。上篇内容介绍 基础替代符号; 中篇内容介绍Java的正则组件以及相关API; 下篇内容特别介绍转义字符的使用。
概述
何为正则表达式?英文称为Regular Expression。 其实这个翻译有点误导性,如果翻译成“规则表达式”,可能更好理解。
设想这么一个场景: 某一天,小舟同学在用word编辑文案,突然意识到某个单词好像拼写有误, 把Java 错写了Jave。 由于文字量比较大,手工一个个替换费时费力,好容易遗漏出错,但是好在word有自动替换功能,一键把全部Jave替换成Java即可。 但是,好景不长,小舟发现有些错误拼写成了Jave,还有些错误拼写成了Javo,还有Javq,Javz, Javd等等,还有连自己都不知道拼写成什么玩意的。
现在该怎么办? 小舟设想,如果有一个功能,能把以Jav开头的,不管第四个字母是什么,全部替换成Java。比如说,Jav*,以*代替任意字母,全部替换成Java。
这种以某些特殊字符来代替另外一些常用字符的表达方式,就称为正则表达式。另外,正则表达式是不限于编程语言的,在一些基本文字软件中,都有此类功能的集成。
基础替代符
在Java中, 把正则表达式的替代符号分为以下几类:
字符匹配符与范围控制符. (点) 任意字符
\s (反斜杠加小写字母s) 空白字符: 回车,制表,空格,换行
\S (反斜杠加大写字母S)非空白字符, 除了上面那四个空白字符
\w (反斜杠加小写字母w)单词字符: 小写字母a-z, 大写字母A-Z, 数字0-9, 下划线 _
\W (反斜杠加大写字母W)非单词字符,除了上面提到的那些单词字符
[ ] (中括号)范围字符[abc]表示字母abc中的任意一个
[a-z]表示字母a到z中的任意一个
[^abc] ^ 为取非, 除了abc, 剩下的全部字符
[abc[def]]这种写法为并集,意为abcdef中的任意一个
[abc&[bce]]& 表示与, 也就是交集。 abc 与上 bce, 最终结果为bc
数量控制符
上面介绍的字符匹配符和范围控制符,一般是与数量控制符组合使用,才能发挥真正威力。 而数量控制符号,分为贪婪型,勉强型,和占有型三种类型,每种类型在不同的场合有不同的用法。
贪婪型? 表示1个或0个。换句话说,表示要不然没有,要不然只有1个
* 表示0个或多个。
+ 表示1个或多个。
{n}表示正好n个
{n,m} 表示n到m个,这是一个左闭右闭区间
{n,}表示至少n个
勉强型
相比于贪婪型,勉强型只是多了一个?:
?? 表示1个或0个。换句话说,表示要不然没有,要不然只有1个
*? 表示0个或多个。
+? 表示1个或多个。
{n}?表示正好n个
{n,m}? 表示n到m个,这是一个左闭右闭区间
{n,}?表示至少n个
占有型
相比于贪婪型,占有型多了一个 +
?+ 表示1个或0个。换句话说,表示要不然没有,要不然只有1个
*+ 表示0个或多个。
++ 表示1个或多个。
{n}+表示正好n个
{n,m}+ 表示n到m个,这是一个左闭右闭区间
{n,}+表示至少n个
不同类型的区别
现在详细分析 贪婪型、勉强型和占有型的区别, 举例如下:
String s = "abcd&"; // 目标字符串
String regular = "\\w+\\w";// 贪婪型正则
String regular2 = "\\w+?\\w";// 勉强型正则
String regular3 = "\\w++\\w";// 占有型正则
Matcher m = Pattern.compile(regular).matcher(s);
// 调用方法去匹配字符串
if(m.find()) {
System.out.println(m.group());
}
分析:贪婪型。\w+, 意为匹配一个或多个单词字符,对于贪婪型,将尽量多的匹配字符,abcd全部匹配出来,直到遇到特殊字符&,匹配结束,然后用\w 去匹配特殊字符&,发现并不匹配,此时将会回溯,也就是说\w+只匹配abc三位,然后让最后一个\w去匹配d,最终\\w+\\w匹配出结果abcd.
勉强型。对于勉强型,会尽量少的匹配字符。既然,+表示一个或多个,那就只匹配一个,所以\w+?最终只匹配出一个a, 然后\w再去匹配一个b,最终匹配结果为ab。
占有型。 占有型和贪婪型很像,刚开始也会尽量多的匹配字符,但是占有型没有回溯机制,遇到不匹配的内容后,将自动停止而不是向前回溯。 所以 \\w++,自动匹配到abcd之后,遇到了&,发现不匹配,然后自动停止了,此时还有一个\w也和&不匹配,所以最终结果匹配失败,输出为空。
位置匹配符
位置匹配符,不匹配任何字符只匹配位置。也就是说,位置匹配符是零宽度的,只匹配位置。^ 匹配一行的开始位置
$ 匹配一行的结束位置
\b 匹配单词边界。 这里的单词指的就是, a-z, 0-9, _, 和\w不同的是,\b匹配的单词还包括汉字。 单词边界匹配,是匹配单词字符和非单词字符之间的位置。
\B匹配非单词边界。和\b,刚好相反, \B 匹配单词与单词之间的位置,或者非单词与非单词之间的位置。
\G匹配前一个匹配结束时的位置。
举例说明:
String s = "a=124_a=789_a=490";
String regular = "^a=\\d+";
比如说只想把开头的a=124找出来,而不关心后面的a,此时就可以用位置匹配符。^匹配一行的开始位置,a= 去匹配 a=, \\d+匹配出124, 所以最终可正确匹配出开始位置的a=124
还有一类特殊的匹配符,虽然严格意义上来说不能算是位置匹配符,但是由于这类匹配符也是零宽度的,所以本文章把这类匹配符归为位置匹配符。 这类特殊的匹配符,称为零宽度断言匹配。(?=exp) 零宽度向前匹配
(?<=exp) 零宽度向后匹配
(?!exp) 零宽度向前非匹配
(?
举例说明2:
// 目标字符串。 只想取出2前面的abc
String s = "abc1 abc1 abc2 abc3";
String regular = "\\w{3}(?=2)";
Matcher m = Pattern.compile(regular).matcher(s);
while(m.find()) {
System.out.println(m.group());
}
就像是$匹配字符串结尾一样,(?=2) 也是匹配字符串中为2的位置,然后再去检查(?=2)前面的\\w{3} 是否匹配abc, 匹配则输出结果。 之所以说是零宽度,是因为(?=2)只是起一个锚点定位作用,此时尾指针指向的不是2后面,而是c后面。
再比如说3:
String s = "aaaaaaaa";
String regular = "aa(?=aa)";
Matcher m = Pattern.compile(regular).matcher(s);
while(m.find()) {
System.out.println(m.group());
}
结果输出将是: aa aa aa
因为,第1次匹配,匹配到前4个a,输出aa; 第2次匹配,是从第2个a后开始向后,而不是第4个a后面,(?=aa) 只标定位置,不占有宽度; 以此类推,最终结果输出为aa aa aa
举例说明4:
对于零宽度向前匹配,和向后匹配方向相反:
// 目标字符串。 只要求输出2后面的abc
String s = "1abc 2abc 3abc";
String regular = "(?<=2)\\w{3}";
Matcher m = Pattern.compile(regular).matcher(s);
while(m.find()) {
System.out.println(m.group());
}
只要求输出2后面的abc,那么就可以用(?<=2)来进行锚点标定,匹配出其后面的字符串。
对于(?!exp) 和 (?
模式说明符(?i) 开启大小写忽略模式,但是只适用于ASCII字符
(?u) 开启utf-8编码模式
(?m) 开启多行匹配模式,.不匹配空白字符
(?s) 单行模式,.匹配任意字符,包括空白字符
(?d) 单行模式,.不匹配空白字符
举例说明:
String test2 = "I care not whether I can succeed\n"
+ "Since in choice of the distance\n"
+ "I simply travel in wind and rain\n"
+ "I care not whether I can win love\n"
+ "Since in deep love with a rose\n"
+ "I just unbosom myself bravely";
String reg = "(?m)\\w{3}$";
在默认情况下,^ 和 $ 只匹配全部字符串的开始与结束位置,但是如果用(?m)开启多行匹配模式之后, ^ 和 $ 将匹配每一行的开始与结束位置。所以说,上面上面那个举例正则(?m)\\w{3}$,将会匹配出每一行的最后三个字符。