上个公司的项目是个视频直播即时聊天的APP,聊天内容有文字,并且文字分好几个部分例如“某某人对某某人说:blabla......”这就已经三个部分了(某某人,某某人,说的内容),每个部分按照需求文字的颜色还要不一样;除此之外,还有表情,当然服务端发过来的都是带有标识的文字,比如一个微笑表情,服务端过来的是这样的:[微笑],你没有看错,是汉字,哎!不说了。当时作的时候用的正则表达式去抠字符串,现在记一下吧,我这人脑子里存不了东西。
正则表达式我的理解就像央视的一个节目《墙来了》,被匹配的字符串就是那些搞怪的参赛者,正则表达式就是墙上面的形状轮廓,匹配的过程就是人摆出的形状和墙上的轮廓是否契合。
精确匹配和字符转义
就是匹配字符或字符串本身,例如Hello,就是要匹配Hello(好傻的表达,知道这个意思就行)。不过一些字符在正则表达式里有特别的含义,匹配这些字符本身,就需要借助“\”转义为普通字符,比如.
在正则表达式中表示除换行符外的任意字符。那要匹配.
本身,就这样了“\.”,That's it。
元字符
不废话,看表格最清楚了:
元字符 | 匹配内容 |
---|---|
. | 匹配除换行符以外的任意字符 |
\w | 匹配字母或数字或下划线或汉字 |
\s | 匹配任意的空白字符(空格、制表符、换页符、换行符、回车符等) |
\d | 匹配任意数字 |
\b | 匹配单词的开始或结束位置 |
^ | 匹配行或字符串开始位置、在字符类中取反 |
$ | 匹配行或字符串结束位置 |
重复匹配
现在假如匹配5个a,这么写aaaaa
有点不那么讲究,这么写a{5}
就舒服多了。当然在重复匹配里也有像元字符一类表示特定重复匹配次数的字符,看表就明白了:
重复匹配规则 | 说明 |
---|---|
* | 重复匹配零次或更多次 |
+ | 重复匹配一次或更多次 |
? | 重复匹配零次或者一次 |
{n} | 重复匹配n次 |
{n,} | 重复匹配n次或更多次 |
{n,m} | 重复匹配n到m次 |
字符类
可以看出元字符定义了一个匹配范围(字符或者位置)。通过字符类我们也可以定义一个匹配范围,很简单,快使用[]
。比如我们现在匹配abcd这个范围的字符串就可以这样写[abcd]
。ok,那么现在定义大写英文字母这个匹配范围,该如何是好?[A-Z]
,That't it!不用解释那个迷人的-
,你该知道ta的用处了。来个例子定义js变量命名规范的正则
:^[a-zA-Z\_\$][a-zA-Z0-9\_\$]*
。
取反
还记得那个^
吗?ta在字符类中还有别的含义,看个例子[^a-z]
表示除了小写英文字母外其它的字符,在字符类中^
还表示取这个字符类相反的匹配范围。字符类可以这样取反,元字符也有相对应的元字符用于对该元字符取反(好厉害的表达水准),而且这样的元字符是非常有规律的,看表找一下呗:
取反元字符 | 匹配说明 |
---|---|
\W | 匹配任意不是字母,数字,下划线,汉字的字符 |
\S | 匹配任意不是空白符的字符 |
\D | 匹配任意非数字的字符 |
\B | 匹配不是单词开始或结束的位置 |
分支条件
这个名字也是看其他人的,不过管他呢。现在有这样一个需求:有a,b两个匹配范围,通过组合就会有4种匹配范围了(a,b,ab,ba),如果一个字符串满足这4种的任一种都算匹配成功。怎么搞?快使用|
,这么写:a|b|ab|ba
。That's it!
分组及后向引用
看到现在你大概也感觉到不管是元字符,还是字符类,ta们(-:瞧瞧这中西合璧的语言风格:-)都定义了一个字符匹配的范围,别的情况还好,但是当重复匹配一组字符的时候就有困难了。例如重复匹配abc三次,不好弄了吧,还是快使用()
吧,(abc){3}
。就是这么简单,就是这么酸爽。
上面只是()
的一个非常小的用法。下面看大招,一个()
就是一个组(匹配小括号字符除外啊),并分配有组号。默认情况下,从左向右,以(
为标记,第一个(
组号为1,然后依次类推。这个组号有啥用,可以引用成功匹配组号对应组定义的规则的字符串内容,注意加重的文字,不是引用组号对应组的匹配规则,而是成功匹配这个规则的字符串本身。怎么引用呢,使用\组号
。
别废话,说例子吧!
\b([a-zA-Z]+)\b\s+\1\b
,这个正则分三部分:\b([a-zA-Z]+)\b
、\s
、\1\b
。现在有Mookaka
匹配第一部分,然后一个空白符Mookaka
,之后\1
就引用第一部分那个分组匹配成功的字符串也就是Mookaka
,所以整个正则可以匹配像Mookaka Mookaka
这样的字符串。
当然像1,2这样的组号看不出所表达的意义,我们也可以自己指定组号,像这样(?<你的组号>匹配规则)
或者(?\
你的组号`匹配规则)。当然有时你不需要什么这个分组啊,引用什么的,就只是想单纯的重复匹配一组字符串,可以这样写**
(?:匹配规则)`**,这样只匹配规则,不分配组号,也不捕获成功匹配的字符串内容。
零宽断言
不纠结这个名字,知道这个名词指的是什么就行。先举一个例子,大家都知道QQ邮箱的格式一般前面是10位数字,然后是@qq.com
,现在我们有一批邮箱地址,想从这些地址中抠出qq号,我们只要qq号,后面什么@qq.com
就不要了,于是你写了一个真正\d{10,11}@qq\.com
,之后你获得的是完整的邮箱地址,接下来你会在构建一个正则什么的来取这个地址字符串前面的qq号。能不能定义这样一种正则,要取的字符串本身匹配定义的正则,并且以这个字符串为基点ta的前面或后面也匹配定义的其它正则,零宽断言就是干这事的。例如上面的例子,构建正则\d{10,11}(?=@qq\.com)
,那么这个正则就直接取前面的qq号。
零宽断言就是定义要获取的匹配正则的字符串位置的前面或后面需要符合的规则,但符合这个规则的字符串不在取出字符串之列,看表格(这里把那个要获取的字符串称为基点字符串吧):
表达式 | 说明 |
---|---|
(?=RegExp) | 基点字符串后跟符合RegExp定义的内容 |
(?<=RegExp) | 基点字符串前面跟符合REgExp定义的内容 |
(?!RegExp) | 基点字符串后面不跟符合RegExp定义的内容 |
(?<!RegExp) | 基点字符串前面不跟符合RegExp定义的内容 |
贪婪、懒惰匹配
一般情况下,正则表达式里含有重复,那么在使整个正则表达式能够匹配的前提下,重复将会匹配尽可能多的字符。看这样一个正则^(\d+)(0*)$
,注意这里有两个分组,如果102300和ta匹配,第一个组获取102300
,第二个组什么也捕获不了,但实际上我们希望组一获取1023
,组二捕获剩下的零的。为了不让正则在有重复匹配时太过贪心,在可能发生贪婪匹配的后面加个?
,That's is。看表格:
懒惰匹配 | 说明 |
---|---|
*? | 重复匹配任意次,但尽可能少重复 |
+? | 重复1次或更多次,但尽可能少重复 |
?? | 重复0次或1次,但尽可能少重复 |
{n,m}? | 重复n次到m次,但尽可能少重复 |
{n,}? | 重复n次以上,但尽可能少重复 |
注释
还可以给正则加注释,开发APP的时候用了很多正则表达式,有的还很长有点复杂,开发组老大看着头晕,提示正则可以加注释。但是实际上也没好多上,反而正则更长看着更难受。用(?#你的注释)
的方式来加注释,看个例子:2[0-4]\d(?#200-249)|25[0-5](?#250-255)|[01]?\d\d?(?#0-199)
。
平衡组
假如现在我要从一个字符串中提取数学表达式,那么提取的字符串中如果有括号,左括号和右括号的数量是一样的。先看一下下面的规则:
- (?'group'RegExp) 把匹配RegExp规则的内容命名为 group,并压入栈中。
- (?'-group'RegExp) 假如匹配RegEXp规则,把最后压入栈且名为group的捕获内容弹出,如果栈是空的,则这个分组的匹配失败。
- (?(group)RegExp1|RegExp2) 假如栈上有名为group的捕获内容,就接着匹配RegExp1表达式,否则继续匹配RegExp2表达式。
- (?!) 总是匹配失败。
总是匹配失败,搞什么?
匹配失败时,正则表达式引擎会进行回溯也就是放弃最前面或最后面的已经捕获了的字符,使整个表达式得到匹配。
这样用(?'bracket' \()
,遇到左括号就在栈里面记一下,遇到右括号,就用(?'-bracket'\))
,把对应的左括号在栈中的记录去掉;如果左括号比右括号多那么最后栈中一定还有记录,那么用(?(bracket)(?!))
故意匹配失败,让正则表达式引擎去掉结果字符串中在栈里有记录的字符串。这样左右括号的数量就平衡了。
网友评论