学会Java正则表达式,不可不看
1.正则表达式作用:
正则表达式一般是一个不确定的字符串,由字符和数字组成,使用正则表达式主要是为了匹配目标字符串中的子字符串
2.字符串的组成:
要熟悉使用正则表达式,必须清楚字符串的组成和正则表达式的匹配原理,字符串由字符和位置组成,比如字符串abc组成如图:
3.Java正则表达式的机制:
正则表达式机制分为两种:
1.DFA(Deterministic Finite Automaton确定性有穷自动机):使用目标字符串(字符串确定)匹配正则表达式
2.NFA(Nondeterministic Finite Automaton非确定性有穷自动机):使用正则表达式(字符串不确定)匹配目标字符串
比如目标字符串s1=“cabdfab”,正则表达式字符串为s2=“ab”。在不使用全局匹配的情况下,DNF的机制是使用s1字符去匹配s2的字符,先用s1的c去匹配s2的a,不匹配,再用s1的a匹配s2的a,匹配成功,再用s1的b匹配s2的b,匹配成功,返回匹配结果;而NFA的机制是使用s2字符去匹配s1字符,先用s2的a去匹配s1的c,不匹配,再用s2的a去匹配s1的a,匹配成功,再用s2的b去匹配s1的b,匹配成功,返回匹配结果。
两种机制的比较:
1.DFA从匹配文本入手,从左到右,每个字符不会匹配两次,它的时间复杂度是多项式的,所以通常情况下,它的速度更快,但支持的特性很少,不支持捕获组、各种引用等等;
2.NFA则是从正则表达式入手,不断读入字符,尝试是否匹配当前正则,不匹配则吐出字符重新尝试,通常它的速度比较慢,最优时间复杂度为多项式的,最差情况为指数级的,但NFA支持更多的特性,因此Java采用了NFA正则表达式机制
4.巧解贪婪模式、勉强模式、占有模式:
4.1其实只有一种数量表示符:
要理解贪婪模式、勉强模式、占有模式这三种模式,得先理解贪婪模式,理解贪婪模式,则要先理解6种数量表示符:?、*、+、{n}、{n,}、{n,m}
,结合X表达式就形成了六种贪婪模式:X?、X*、X+、X{n}、X{n,}、X{n,m}
,但其实就只有一种贪婪模式X{n,m}
,其中X{n,}
等价于m取无穷大,X{n}等价于X{n,n}
,X+
等价于X{1,}
,X*
等价于X{0,}
,X?
等价于X{0,1}
,所以只要理解了贪婪模式的X{n,m
},就理解了贪婪模式
4.2怎么理解违反常识的n=0:
继续上文,X{n,m}
表示最少匹配X表达式n次,最多匹配X表达式m次,当n为0时是最让人犯糊涂的,因为匹配相当于比较,n为0,代表比较0次,我们的生活中很少有这样的说法,比如NBA湖人队和NBA火箭队重来没有打过比赛,你会给你朋友说湖人队和火箭队打了0次比赛吗?可能会(你朋友估计会用看智障的眼神看你),但很少。通过这样的例子,0的意思很明显了,0表示没有,用英语说就是nothing happen,没有发生的事。现在假设目标字符串s1=“abceda”,正则表达式s2=“a{0}”,就表示字符a就不和目标字符串s1="abceda"的字符去逐个比较了,那都不比较了呗,a{0}自然无法匹配目标字符串s1的子字符串,有意义吗?从字面上说完全没有意义,但是把这个匹配放到http://c.runoob.com/front-end/854下测试,又见到这样的情况:
是不是很神奇,显示有7处匹配但是又没有具体的匹配字符,那么神奇在哪些地方呢,下面说下:
神奇1:按照0在生活中的说法,0表示没有发生,a{0}
不会去匹配字符串s1=“abceda”,自然不会出现"找到匹配"这样的字样
神奇2:目标字符串s1="abceda"只有6个字符,却出现了7处匹配这样的结果;
神奇3:显示有7处匹配但是又没有显示具体匹配的字符;
综上所述,就表示当n=0时,正则表达式的匹配规则和我们想象中的匹配常识有着很多不同的地方,现在针对结果做下分析:
1.共有7处匹配:也就说a{0}
匹配了s1=“abceda"的7个地方
2.没有出现匹配字符:也就说a{0}
匹配的7个地方,是不能用字符表示的,不能用字符表示,就说明匹配的不是字符
那这7个地方是哪7个地方呢,看下前面我们说的字符串组成,字符串是由字符和位置组成的,abceda共有6个字符和7个位置组成的,不难想到a{0}
匹配的就是这7个位置,那验证一下吧,在http://c.runoob.com/front-end/854中输入替换文本,出现如下结果:
当然也可以通过Java代码验证,结果如下:
从上面可知当n=0,X{0}
不会去和目标字符串的字符匹配,而是和目标字符串的位置去匹配,因此a{0},b{0},c{0},abc{0}
作为正则表达式去匹配目标字符串的时候效果是一样的(读者可以把上面的a{0}
换做b{0}
测试一下)。综上所述,这种机制堪称玄学,a{0},b{0},c{0},abc{0}
都可看做X{0},
0代表无,字符串的位置没有通过显然可见的方式显示,因此也是无,所以X{0}
去匹配目标字符串的位置相当于用无去匹配无。
4.3.先说贪婪模式下的贪婪问题和回溯问题:
4.3.1贪婪问题:
举个例子读者就知道了,假设目标字符串为s1="baaabcaabd"
,正则表达式为s2="a+d"
,前面我们说过a+
相当于a{1,}
,因此s2
相当于s2="a{1,}d"
,在全局搜索的情况下将匹配s1=“baaabcaabd"的子串"aaab"和"aab”,通过网址http://c.runoob.com/front-end/854验证如下:
由于Java采用的是NFA机制,所以这两个结果得出的过程如下:
匹配aaab的过程:s2="a{1,}d"
去匹配s1="baaabcaabd"
,s2会先用a先去匹配s1第一个字符b,不匹配,再去匹配s1的第二个字符a,匹配成功,满足了n=1最少匹配一次的问题,接着s1的a由于使用了贪婪模式,因此a还不满足,还要继续去匹配s2的第三个字符a,匹配成功,接着s1中的a还不满足,继续去匹配s2的第四个字符a,匹配成功,接着s1中的a还不满足,要去匹配s2的第五个字符b,匹配失败,然后用s1中的b去匹配s2的第五个字符b,匹配成功,因此s2就匹配了s1中的字符子串aaab。
匹配aab的过程:
匹配aab的过程相当于用s2="a{1,}d"
去匹配s1="caabd"
,根据前面的思路,很容易就知道了,在此不述。
4.3.2回溯问题:
再次使用个例子说下,假如s1仍为s1="baabd"
,s2="\w+a"
,那么s2将会匹配s1的子串"baa”(读者可通过上面的正则表达式网站验证),为什么会这样呢?因为s2其实相当于s2="\w{1,}a"
,\w
表示单词字符,因此\w可匹配所有字符,其匹配过程如下图:
4.4再说n=0:
前面说过,其实只有一种数量标识符X{n,m}
,X{0}
相当于X{0,0}
,因此包含n=0的情况还有X{0,}
和X{0,1}
(假设m=1),那么这三种情况X{0},X{0,}X{0,1}
有什么区别呢?假设目标字符串为s1=“abaad”,通过下图匹配结果分析:
a{0}的作用前面我们说过了用于匹配字符串的位置(其实是匹配了空字符串),因为使用了全局搜索,因此可以匹配到6处位置,这里就不多说了,至于a{0,1}
匹配目标字符串"abaad"的过程,则是先用a{0}匹配其第一个位置,匹配成功,然后因为a{0,1}
是贪婪模式,所以会再次使用a去匹配"abaad"的第一个字符a,此时达到最大匹配次数1,不能再用a去匹配剩下的字符串,第一次匹配成功,依次下去匹配,匹配过程如下图:
而a{0,}
匹配目标字符串"abaad"的匹配过程如下:
4.5先更到这儿吧,等有时间再说下Java正则表达式其他一些需要注意的地方