前言
在编程语言中,除了容器集合、多线程并发、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
比如说只想把开头的a=124找出来,而不关心后面的a,此时就可以用位置匹配符。^匹配一行的开始位置,a= 去匹配 a=, d+匹配出124, 所以最终可正确匹配出开始位置的a=124
还有一类特殊的匹配符,虽然严格意义上来说不能算是位置匹配符,但是由于这类匹配符也是零宽度的,所以本文章把这类匹配符归为位置匹配符。 这类特殊的匹配符,称为零宽度断言匹配。
- (?=exp) 零宽度向前匹配
- (?<=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) 和 (?<!exp) 这两个非匹配断言,和匹配断言正好相反。 (?!exp) 是要求匹配内容的后面,不能有指定的内容; (?<!exp) 要求匹配的前面不能有指定的内容。
模式说明符
- (?i) 开启大小写忽略模式,但是只适用于ASCII字符
- (?u) 开启utf-8编码模式
- (?m) 开启多行匹配模式,.不匹配空白字符
- (?s) 单行模式,.匹配任意字符,包括空白字符
- (?d) 单行模式,.不匹配空白字符
举例说明:
String test2 = "I care not whether I can succeedn"
+ "Since in choice of the distancen"
+ "I simply travel in wind and rainn"
+ "I care not whether I can win loven"
+ "Since in deep love with a rosen"
+ "I just unbosom myself bravely";
String reg = "(?m)w{3}$";
在默认情况下,^ 和 $ 只匹配全部字符串的开始与结束位置,但是如果用(?m)开启多行匹配模式之后, ^ 和 $ 将匹配每一行的开始与结束位置。所以说,上面上面那个举例正则(?m)w{3}$,将会匹配出每一行的最后三个字符。