深入浅出正则表达式

来源:互联网 发布:i wrote python 编辑:程序博客网 时间:2024/05/19 03:25

正则表达式其实还是蛮简单的,因为它没有很强的逻辑在里头,我们只需要按照它的规则从头开始依次去读就好了

话虽这么说,我相信很多人一开始接触正则,当看到那一连串“乱七八糟”的字符时,都会一脸懵逼,这是啥玩意?然后就自然而然的认为这东西很复杂,接着打心底就抵触使用正则,然后开始在网上搜看是否有现成的表达式可用,或者直接写代码来实现字符匹配。

我自己就是这样

最近终于花时间把这块东西看了下,下面是学习总结,便于后续在淡忘时回头温故

 

字符串匹配,无非就是基于如下规则:

1:从被匹配字符串中确认起始匹配位置

2:基于该位置,对后续字符依次逐个进行匹配,如果全部相等,则匹配成功,否则失败

 

正则表达式也是基于如上规则来进行匹配的

 

如何匹配?

先来看一个简单的例子:

String str = "d123te:testadad46587:asd-test111test";Pattern pt = Pattern.compile("test");Matcher match = pt.matcher(str);while (match.find()) {    System.out.println(match.group());}

输出结果:

testtesttest

可以把整个表达式作为一个分组,那么这个分组会在目标字符串中从头开始匹配,这个例子很明显,test匹配三次 

 

对于分组内部表达式的匹配,则是对表达式所描述的字符串与目标索引下的字符做完整的匹配,如果全部匹配,则ok

 

拿这个例子来说,一开始d跟t就不符,那肯定pass,然后匹配索引+1,继续往前走,发现te匹配,但是s跟:不匹配,继续往前走,直到碰到跟test完整匹配的,然后保存匹配结果,然后继续匹配,直到结束

 

正则的基础匹配规则介绍完了

 

通过上面的描述,我们可以得出表达式所能定义的:

1:大分组匹配的起始位置

2:目标字符串的描述,包括字符类型,字符长度等等

 

匹配起始位置(边界匹配)

^ 行的开头

$ 行的结尾

\b 单词边界, 就是不是单词字符的地方。

分组断言

 

边界匹配符只能出现在表达式的开始或者结束位置,比如:

^a{3}

a{3}$

\baaa

Aaaa\b

 

分组断言也可以指定要匹配的开始位置,这个后面再介绍

 

字符匹配描述

起始位置确定了,接下去基于该位置开始逐个比对,这就需要在表达式里逐个描述要匹配的字符

 

描述方式有三种:

1:字符

   就是显示的描述要匹配的字符,比如a,b,c,-,@等等

2:字符类

   描述要匹配的某一类字符

[abc] a、b 或 c(简单类)

    [^abc] 任何字符,除了 a、b 或 c(否定)

    [a-zA-Z] a到 z 或 A到 Z,两头的字母包括在内(范围)

    [0-9] 0到9的字符都包括

    . 任何字符。我的就是.字符本身,怎么表示呢? \.

    \d 数字:[0-9]

    \D 非数字:[^\d]/[^0-9]

    \w 单词字符:[a-zA-Z_0-9]

\W 非字符[^\w]

[abc]其实等同于[a|b|c], 同样的,[a-z0-9]也等同于[a-z|0-9],都是或的关系

 

单个描述某一字符,肯定不够,还需要量词来描述要匹配的多个字符

 

量词:

量词默认都是取重复的上限 

"*"  重复零次或更多

 例如"aaaaaaaa"匹配字符串中所有的a  正则: "a*"   会出到所有的字符"a"

 "+"  重复一次或更多次

    例如"aaaaaaaa" 匹配字符串中所有的a  正则: "a+"  会取到字符中所有的a字符,  "a+""a*"不同在于"+"至少是一次而"*"可以是0次,

 "?"   重复零次或一次

    例如"aaaaaaaa" 匹配字符串中的a正则 "a?"只会匹配一次,也就是结果只是单个字符a

"{n}"  重复n

    例如从"aaaaaaaa" 匹配字符串的a并重复3正则:  "a{3}"  结果就是取到3a字符  "aaa";

"{n,m}"  重复nm

    例如正则"a{3,4}"a重复匹配3次或者4所以供匹配的字符可以是三个"aaa"也可以是四个"aaaa"正则都可以匹配到

 "{n,}"  重复n次或更多次,无上限

 

懒惰限定符

上面的量词默认都是指定重复上限的,那就需要一个限定符,可以指定重复下限,意思是,尽量少重复或者不重复

 

懒惰限定符就是在上面的量词符号后面加个 ? 号

比如:

*?, +?, ??, {n}?, {n,m}?, {n,}?

 

加了懒惰限定符后,默认都是取重复的下限

 

分组

上面说过整个表达式是一个大的分组,其实在表达式内部,还可以指定分组,这些分组在表达式被解析的时候,会从0开始依次分配组号

 

在正则中,用()来表示一个分组

 

分组的作用我觉得有两个:

1:将某些特定的子表达式分成一组,便于后续表达式复用和以组为单位进行逻辑操作

2:根据组号,对特定组的表达式匹配结果进行后向引用

 

看个例子:

String str = " 15916898088";Pattern pt = Pattern.compile("^(15([0-3]|[5-9]))");Matcher match = pt.matcher(str);while (match.find()) {         System.out.println(match.group());}

 

这个例子表达式有三个分组,分别是:

0:^(15([0-3]|[5-9]))

1:(15([0-3]|[5-9]))

2:([0-3]|[5-9])

 

第一个分组就是整个表达式,对应索引为0,内部分组的索引,则按照从左到右,从外到内依次排列

 

上面表达式的执行结果为:159

 

接着看通过分组索引后向引用的例子:

 

String str = "15916898088";Pattern pt = Pattern.compile("^(15([0-3]|[5-9])).*\\2");Matcher match = pt.matcher(str);while (match.find()) {         System.out.println(match.group());}


表达式中的\2表明要引用分组2的匹配结果,这里很明显为9 

所以这个表达式的执行结果为:1591689

 

如果我们只想匹配结果,不希望捕获匹配的结果和对分组分配组号,只需要在分组表达式前面加上 ?: 就可以了,比如:

^(15(?:[0-3]|[5-9])).*

分组断言(零宽断言)

上面在匹配起始位置中提过,可以通过分组断言来指定起始位置,其实说白了就是:

 

先通过一个分组表达式找到匹配的字符串,然后根据该匹配字符串的位置,来决定后续表达式的起始位置

 

举个例子:

“ iloveyouforever”

 

现在如果有个分组表达式(you), 我们通过这个表达式的结果,至少可以确定两个位置,一个是you前面的position,紧接着e,还有就是you后面的position,紧接着f

 

所以,断言语法就是用来确定上面两个位置的

 

(?=you)

匹配you前面的position,比如:

表达式 "(?=you).*"的匹配结果为:youforever

表达式 ".*(?=you)"的匹配结果为: ilove

 

(?<=you)

匹配you后面的position

表达式 "(?<=you).*"的匹配结果为:forever

表达式 ".*(?<=you)"的匹配结果为: iloveyou

 

另外(?!you)(?<!you)则是表达式取反,position的确定则跟上面一致

 

Over

 

参考文献:

https://www.cnblogs.com/zery/p/3438845.html


原创粉丝点击