正则表达式刍议
其实我已想了解正则表达式很久了,直到今日教材上(指《JavaSE6编程指南》)提到我才开始系统地了解它。到现在才了解到,正则式没有经过长时间的学习和应用,是不可谓之“已系统了解正则式”。鄙人经过几个小时的努力学习,应算入门了吧!?期间查阅网上资料之时固然有不少相关资料,或是转载他人,千遍一律;或是言之高深,毫无头绪;或是非java平台,多有异处。唯CSDN论坛上,网友火龙果,翻译了sun公司上的正则式入门教材,观后多有裨益,在此感谢。多言无益,开始。
语法
先是一些设想:正则式中的一些语法,与普通语言一般有其规律、有基础和建立于基础之上的“高级”语法。介绍之时,我想尽量的有一些层次,毕竟思考层次的划分问题的时候,其实是对正则式本质的思考,有利于提高。
正则表达式,其名中含“正”、“则”二字,均有规范之意。名副其实,正则表达式确实是检查某个字符串中,符合正则式规范的字符串。如字符串:“abc”,之后用正则式“a”来作为规范,可找出字符串中的“a”。还是先把语法都粘贴上来吧。说到语法,鄙人还有个小小的理解:其实是把正则式中可能出现的字符都罗列出来而已。
1
普通字符x
x包括字母、数字、汉字、下划线
2
/n、/t
新行(换行)符 ('/u000A')、制表符 ('/u0009')
3
/f、/r
换页符 ('/u000C') 、回车符 ('/u000D’)
字符集合
4
[XYZ]
X、Y或 Z
5
[X-Y]
X到Y的所以字符
6
[^abc]
任何字符,除了a、b或 c(补集)
7
[a-zA-Z]
a到z或A到Z,两头的字母包括在内(并集)
8
[a-d[m-p]]
a到d 或 m 到 p,可表示为[a-dm-p] (并集)
9
[a-z&&[def]]
d、e或f(交集)
10
[a-[a-z&[^bc]]
a到z,除了b和c,可表示为 [ad-z](差集)
预定义字符
11
.
除了换行外的任何字符(开了某个模式后,表示任何字符)
12
/d
数字[0-9]
13
/D
非数字[^0-9]
14
/s
空白字符,空格、制表符、换页符等
15
/S
非空白字符,可表示为^/s
16
/w
单词符:[a-zA-Z_0-9],字母、数字、下划线
17
/W
非单词符:[^/w]
边界适配器
18
^
行的开头(不表示任何字符)
19
$
行的结尾(不表示任何字符)
20
/b
单词的结尾
21
/B
非单词结尾
22
/G
上一个匹配的结尾
量词
23
X{n}
X恰好出现n次
24
X{n,m}
X至少出现n,而至多m次
25
X{n,}
X至少出现n次
26
X?
X出现1次或不出现,等价于X{0,1}
27
X+
X出现1次至多次,等价于X{1,}
28
X*
X出现0到多次,等价于X{0,}
逻辑运算符
29
XY
X后跟Y
30
X|Y
X或者Y
31
(…)
括号内做捕获组
特殊符
32
?
用于量词后,有无表示贪婪与非贪婪模式
33
+
用于量词后,开启消极(possessive)模式
34
/
转义符,如表示$、(、)分别表示为/$、/(、/)
35
/N(N为一数字)
有捕获组的情况下,N表示引用第N组捕获组
当然,全部的正则构造不止这些。
为了更好的解释,一般都会有例子的。但不可能把所有的例子都编一个程序,哪未免太烦了,所以约定正则式用p=……表示;需要匹配的字符串用m=……表示,匹配结果就用(p,m)=……表示,如果有多个字符串,用逗号隔(……表示字符串,并且省去双引号。如果结果为空,加双引号强调。)。
基本
正则式最基础的东西就是字符。包括:①正则式中有特殊含义的字符,如{、}、?、$等,这些特殊含义的字符称为元字符,用转义字符“/”即可表示一般字符;②一般的字符,包括字母、数字等可见却不是元字符的字符;③不可见字符,如/n、/t、/f,空格等。正则式中使用转义字符的时候要注意,要用两个”/”才能表示你使用了转义符。另外,元字符“.”可表示上述的除换行符所有基本字符,开了某个模式(指:Pattern.DOTALL模式),就可以包括/n了。
eg1、 p=a, m=aaa, (p,m)=a,a,a
eg2、 p=/n, m=/n/t, (p,m)=/n
eg3、 p=//(, m=(){}, (p,m)=(
eg4、 p=., m=){/na%^, (p,m)=),},a,%,^
逻辑运算符
取逻辑运算符之名,汗颜之极,这里实在是为了与命题逻辑联系起来。逻辑符其实是第二基本的东西了,表示两个字符间的关系。
28、XY,X且Y,因为m中字符不可能既是X又是Y,所以只能解释为X后面跟Y,所有m种满足”XY”格式的都可以匹配成功;
29、X|Y,X或Y,m中字符是X和Y都可以匹配成功;
5、[^X],非X,除外的所以字符都可匹配成功。这里多了个[],是为了区别于18;
30、(…),捕获组,如你所想,把括号类看作一个整体。每个整体都有自己的编号,如((ab)(cd))有三个组,abcd、ab、cd分别编号1、2、3。这样做有两个好处:量词从修饰一个字符扩充到多个;对匹配后的所得的字符进行分组,方便提取。如,p=(aa)a,m=aaa, (p,m)=aaa,结果虽然与未加括号一般,但如果我说,我要第一组,系统就会打印aa。
eg1、 P=ab, m=aabbaabb, (p,m)=ab,ab
eg2、 P=a|b, m=aabbaabb, (p,m)=a,a,b,b,a,a,b,b
eg3、 P=[^a], m=ab*(/n, (p,m)=b,*,(,/n
字符集合
“字符集合”一词是鄙人取的,API上唤作“字符”,虽然匹配的结果是单个字符,亦不甚妥当。这里的字符集合其实是逻辑运算符中“|”的升级版。即[abc]=a|b|c。这里把它称为“集合”是参考集合表示法中的列元素法。即把所有可能的字符都列在一个集合中,如果m某个字符属于这个集合,就匹配成功了。
4、[XYZ],X或Y或Z;
5、[X-Y],4的升级版,表示字符X到Y的所有字符,”-”,从什么到什么之间的东西。应该是按unicode码的顺序。但是我们一般是用来方便表示0-9,a-z,A-Z的子集的,不会书写如[&-^]这样的东西(谁知道unicode中在&和^之间是什么东西?)。
eg1、 P=[a-c], m=abcd&*/n, (p,m)=a,b,c
eg2、 P=[abc], m=abcd&*/n, (p,m)=a,b,c
字符集合的运算
这里可以真正地称为“运算”了。注意这里的运算结果依然是字符集合,匹配的结果依然是一个字符(或者匹配不成功,啥都没有)。
补集:[^X],除X外的字符。或者[^X-Y],除除X到Y之间字符外的所有字符;
并集:[X1-Y1[X2-Y2]]或[X1-Y1X2-Y2],从X1到Y1的所有字符或者从X2到Y3的所有字符,比如[a-c[e-f]]表示a|b|c|e|f;
交集:[X1-Y2&&[X1-Y2]]或者[X1-Y2&&X1-Y2],从X1到Y1的所有字符和从X2到Y2的所有字符的所有公共字符,比如[a-d&&[c-f]]=c|d;
差集:[X1-Y2&&[^X1-Y2]](注意不能写成[X1-Y2&&^X1-Y2]),从X1到Y1的所有字符去掉X2到Y2的所有字符。比如[a-d&&[^c-f]]=a|b。
eg1、 p=[12[67]], m=0123456789, (p,m)=1,2,6,7
eg2、 p=[12&&[67]], m=0123456789, (p,m)=""
eg3、 p=[12&&[2-8]], m=0123456789, (p,m)=2
eg4、 p=[12&&[^2-8]], m=0123456789, (p,m)=1
预定义字符
与数学上将自然数集、实数集写成N、R一样,正则式某些字符集合也可以用字母表示,这个字母的大写,表示其补集。
12、/d,数字集,即[0-9];
13、/D,非数字集,即[^0-9]
14、/s空白字符集,即/n|/t| |/f…
15、/S非空白字符集,即^/s
16、/w单词字符集,即[0-9[a-z][A-Z][_]],包括下划线“_”
17、/W非单词字符集,即^/w
eg1、 P=//d, m=12abAB_/n/t%*, (p,m)=1,2
eg2、 P=//D, m=12abAB_/n/t%*, (p,m)=a,b,A,B,_,/n,/t,%,*
eg3、 P=//s, m=12abAB_/n/t%*, (p,m)=/n,/t
eg4、 P=//S, m=12abAB_/n/t%*, (p,m)=1,2,a,b,A,B,_,&,*
eg5、 P=//w, m=12abAB_/n/t%*, (p,m)=1,2,a,b,A,B,_
eg6、 P=//W, m=12abAB_/n/t%*, (p,m)=/n,/t,%,*
量词
到目前为止,我们都是每次输出单个字符。如果要指定某个字符或字符串出现特定的次数,就要使用量词了。
23、X{n},X恰好出 现n次;
24、X{n,m},X出现的 次数在n到m直接(包括首尾);
25、X{n,},X至少出 现n次
26、X?,X出现一次或不出现;“?”表示出现 了吗?,答案只能回答有或这没有,即1或0,等价于X{0,1};
27、X+,X出现1次至多次,等价于X{1,};
28、X*,X出现0次到多次,等价于X{0,}。
上述的X,可以表示单个字符、字符集合、捕获组。
eg1、 P=a{0}, m=abaaab, (p,m)="","","","","","",""(7个)。
eg2、 P=a{2}, m=abaaab, (p,m)=aa
eg3、 P=a{0,2}, m=abaaab, (p,m)=a,"","",aa,a,"",""
eg4、 P=a{2,}, m=abaaab, (p,m)=aaa
eg5、 P=a?, m=abaaab, (p,m)=a,"",a,a,a,"",""
eg6、 P=a+, m=abaaab, (p,m)=a,aaa
eg7、 P=a*, m=abaaab, (p,m)=a,"",aaa,"",""
eg8、 P=(ab)?, m=abaaab, (p,m)=ab,"","",ab,""
eg9、 P=[ab]?, m=abaaab, (p,m)=a,b,a,a,a,b,""""
eg10、 P=[ab]{2}, m=abaaab, (p,m)=ab,aa,ab
eg1中,X{0}叫零长度匹配,用它来做正则式,开头、结尾、两字符间无字符(字符串长度为零)都可以叫匹配成功。
说明一下这零长度匹配问题:在贪婪模式下(下面有),量词会先读入整个字符串,匹配的话,返回,不匹配的话,退一格再匹配,之后重复,至左边无字符可退为止。于是对于零长度匹配可以这样理解:a{0}首先匹配整个字符串,不含a,匹配成功,返回””。而后退一个字符,也不含a,匹配成功,返回””。如此这般,共试7次,返回7个""。
边界适配器
边界适配器是用来控制字符串边缘的取舍。
18、^,行的开始,不表示任何字符。
19、$,行的结束,不表示任何字符。^与$性质一样,对于^X&,只要X前面有任何字符(包括可见与非可见),后面有任何字符,都不能匹配成功。就是说,X的两边必须无任何字符才能成功。
20、/b,单词的结尾,与^、$性质差不多,对于/bX/b,只要X的前面或后有任何的非空格字符,匹配都不成功。就是说,X的两边必须是空格或无字符才能成功。
21、/B,非单词结尾,对于/BX/B,只要X的两边有空格或者无字符或者非可见字符,匹配不成功。就是说,X必须在某个连续字符串的中间(首尾不行)。
22、/G,上一个匹配的结尾。
eg1、 P=a{0}$, m=abaaab, (p,m)=""
eg2、 P=^dog&, m=/ndog, (p,m)=无可匹配的
eg3、 P=//bplay//b, m=I play it, (p,m)=play
eg4、 P=//bplay//b, m=I am player, (p,m)=无可匹配的
eg5、 P=//BMM//d//B, m=MM1GMM2G/nMM3/t MM4, (p,m)=MM2
eg6、 P=//Gdog//d, m=dog1dog2, (p,m)=dog1,dog2
eg7、 P=//Gdog//d, m=dog1 dog2, (p,m)=dog1
这里说明一下eg6和eg7的结果:首先是,字符串开始的部分是默认已匹配的,所以eg6出现两个“dog”。eg7不出现dog2是因为dog2前面的空格是未匹配的。
特殊符
特殊符能完成其他元字符所不能完成的功能。
32、?,关闭贪婪模式。这个模式是这样工作的:首先匹配整个字符串,匹配成功则返回,退格再匹配;不成则退格再匹配,至左方无任何字符。注意,前一次匹配成功后所返回的字符串在后一次匹配中是不能用的。这个模式下,按最小限度的可能匹配。
如果量词后没有?,则开启贪婪模式(正常模式),这个模式是这样工作的:从字符串开始出开始匹配,成功则返回,进格再匹配;不成则直接进格匹配,至右方无任何字符。与非贪婪模式一样,前一次匹配成功后所返回的字符串在后一次匹配中是不能用的。这个模式下,按最大限度可能匹配。
33、+,开启消极模式。这个模式是这样工作的:一开就匹配整个字符串,成功则返回,结束匹配;不成也结束匹配。量词后没有+,为正常模式。
34、/,转义符。前面已说,这里不再赘叙。
35、/N,引用捕获组。这相当于一个代词,所代的,是编号为N的捕获组。如((12)(34)),1234、12、34编号分别为1、2、3。正则式((12)(34))/2等价于123412
eg1、 P=.*foo, m=xfooxxxfoo, (p,m)= xfooxxxfoo
eg2、 P=.*?foo, m=xfooxxxfoo, (p,m)=xfoo,xxxfoo
eg3、 P=.*+foo, m=xfooxxxfoo, (p,m)=无可匹配的
eg4、 P=((a)(b))//2, m=abab, (p,m)=aba
这里说明一下eg3,消极模式,一开始就匹配整个字符串,而.*将整个字符串都匹配了,foo匹配不上,故结果为无可匹配的。
Java中的正则式
其实,到现在才是真正的主题(众人纷纷离场-_-!)。不错,写了这么多还没说如何在java中运用正则表达式,懂了语法也无用。
要使用正则式,一般要使用JDK1.4新增的专门支持正则式的包java.util.regex中的Pattern类和Matcher类,这两个类的对象分别是正则表达式,要进行匹配的字符串或字符序列。Pattern类的构造方法为private型,要使用静态工厂方法创建新对象。而Matcher类创建对象一般是要用Pattern对象的matcher()方法。先给个例子。
首先是引入Pattern和Matcher类。而后通过Pattern类的静态工厂方法compile()创建对象,再通过Pattern对象调用matcher()方法来创建Mattcher的对象,从而正则式就与要匹配的字符串建立了关系。
知道了基本使用方法,下一步就是使用两个类中的定义的其他方法了。
Pattern类
<Mehtod1> public String pattern()
返回正则表达式。如:
Pattern pa=Pattern.compile("^//Dacd//b&");
System.out.println(pa.pattern());
打印的是^/Dacd/b&
<Mehtod2> public String[] split(String s)
<Mehtod3> public String[] split(String s,int limit)
这两个方法用于按特定的正则式切割字符串s,前者为全部切完,后者为切割limit分,如
结果:
one two three four five
one two:three;four:five;
注意到第二句one前有“;”,则“;”前面的部分别切割,虽然是无字符,但还是照样切割。
Matcher类
<Mehtod1> public boolean matches()
对整个字符串用特定的正则式匹配,整个字符串匹配成功时,返回true。
<Mehtod2> public boolean lookingAt()
检测字符串是否以特定的正则式规范开始,是则返回true。如
Pattern p = Pattern.compile("ab");
Matcher m = p.matcher("abab");
System.out.println(m.matches());
System.out.println(m.lookingAt());
则顺序打印false、true。
<Mehtod3> public boolean find()
以特定正则式为模板,从字符串开始进行寻找,找到符合的返回true。这里可能不是单纯地找,找到后还要传递目标字符串。
<Mehtod4> public int start()
若匹配成功,该方法返回第一个字符在目标字符序列中的索引,一般与find()连用。
<Mehtod5> public int end()
若匹配成功,该方法返回最后一个字符在目标字符序列中的索引+1,一般与find()连用。
结果:
Match number 1 start(): 0 end(): 3
Match number 2 start(): 4 end(): 7
Match number 3 start(): 8 end(): 11
<Mehtod6> public String replaceFirst(String replacement)
该方法时Matcher的对象调用的,整个对象包含的信息有字符串、正则式。这个方法的作用是将要检验字符串中符合正则式规范所有的字符串中的第一个换为replacement。返回所得到的新字符串。
<Mehtod7> public String replaceAll(String replaecment)
与replaceFirst()稍稍不同的是,这个方法时换所有符合正则式规范的字符串。
结果:
The cat says meow. All cats say meow.
The cat says meow. All dogs say meow.
<Mehtod8> public Matcher appendReplacement(StringBuffer sb,String replacement)
该方法将目标字符串与指定正则式规范相匹配的子字符串全部换为指定的字符串:replacement,并将结果添加到StringBuffer的对象sb中。
<Mehtod9> public StringBuffer appendTail(StringBuffer sb)
将目标字符序列中最后一次替换后剩下的字符序列添加到StringBuffer的对象sb中。
结果:
初始字符串为:现在的时刻为10:51pm,这里的营业时间为:每天8:00am至5:30pm!!!
最后的结果为:现在的时刻为22:51,这里的营业时间为:每天8:00至17:30!!!
<Mehtod10> public int groupCount()
返回与之相配的正则式中的捕获组个数。
<Mehtod11> public String group(int group)
返回匹配的子字符串中捕获组编号为group的子字符串。
<Mehtod12> public String group()
返回匹配的子字符串。
结果:
2
ABCD1
ABCD2
①若改第八行为System.out.println(m.group(0));结果一样,说明在不为整个字符串分组的情况下,整体字符串编号为0;②若改为System.out.println(m.group(1));结果中ABCD1、ABCD2分别变成AB、AB;③若改为System.out.println(m.group(2)); 果中ABCD1、ABCD2分别变成CD1、CD2。
String类中正则式使用
简单情况下,使用没必要使用专业的Pattern和Match类,String类也提供了数个很多基于正则式的方法。
<Mehtod13> public boolen matches(String regex)
当整个字符串满足指定的正则式regex,返回True。
此方法与Matcher类中的matcher()方法功能一样。
String s2="Anther 99-23-156";
System.out.println(s1.matches("//d{2}-//d{2}-//d{3}"));
System.out.println(s2.matches("//d{2}-//d{2}-//d{3}"));
则分别打印true、false。
<Mehtod14> public String replaceAll(String regex,String replacement )
<Mehtod15> public String replaceFirst(String regex,String replacement)
此两方法与Matcher类中的同名方法原理一致。
<Mehtod16> public String[] split(String regex)
<Mehtod17> public String[] split(String regex int limit)
此两方法与Pattern类中的同名方法原理一致。
花了半天(非夸张,是真正的半天,6个小时),终于完成了。感悟有二:①文章原创确实不易,共8页纸却花去6+个小时。每一句话不单都是要码字码出来的,而且每写一句之前要思前想后,看它是否正确、与前文是否有矛盾之处,同时还要注意整篇文章的逻辑与层次;②虽然花去这么多时间,却还是值得的:有利于加深印象,留下人生的足迹。