一、基本概念:
在我们写页面时,常需要对表单数据如账号、密码、身份证号等进行验证,而最有效、用的最多的就是使用正则表达式来验证。
正则表达式(Regular Expression)是用于描述一组字符串特征的模式,用于匹配特定的字符串。其应用非常广泛,特别是在字符串处理方面,应用如下:
1、验证字符串,即验证给定的字符串或子字符串是否符合指定的特征,可验证邮箱地址等。
2、查找字符串,从给定的文本中查找符号指定特征的字符串,比查找固定字符串更灵活。
3、替换字符串,即查找到符合某特征的字符串后将其替换。
4、提取字符串,即从给定的字符串中提取出符合指定特征的子字符串。
二、检测工具介绍
介绍一个工具RegexBuddy
三、结构总体介绍
1、字符字面量:
匹配一个具体字符,包括不用转义的和需要转义的。比如a匹配字符"a",又比如\n匹配换行符,又比如\.匹配小数点。
2、字符组:
匹配一个字符,可以是多种可能之一,比如[0-9],表示匹配一个数字。也有\d的简写形式。另外还有反义字符组,表示可以是除了特定字符之外任何一个字符,比如[^0-9],表示一个非数字字符,也有\D的简写形式。
3、量词:
表示一个字符连续出现,比如a{1,3}表示“a”字符连续出现1到3次。另外还有常见的简写形式,比如a+表示“a”字符连续出现至少一次。
4、锚点:
匹配一个位置,而不是字符。比如^匹配字符串的开头,又比如\b匹配单词边界,又比如(?=\d)表示数字前面的位置。
5、分组:
用括号表示一个整体,比如(ab)+,表示"ab"两个字符连续出现多次,也可以使用非捕获分组(?:ab)+。
6、分支:
多个子表达式多选一,比如abc|bcd,表达式匹配"abc"或者"bcd"字符子串。
7、反向引用:
比如\2,表示引用第2个分组。
四、结构详细介绍
4.1 字符组
虽然是叫字符组,但只是表示其中一个字符,例如[abc],表示匹配一个字符,它可以是“a”、“b”、“c”之一。
(1)范围表示法:
比如[123456abcdefGHIJKLM],可以写成[1-6a-fG-M]。用连字符-来省略和简写。
要匹配“a”、“-”、“z”这三者中任意一个字符,该怎么做呢?不能写成[a-z],因为其表示小写字符中的任何一个字符。可以写成如下的方式:[-az]或[az-]或[a\-z]。即要么放在开头,要么放在结尾,要么转义。总之不会让引擎认为是范围表示法就行。
(2)排除字符组:
某位字符可以是任何东西,但就不能是"a"、"b"、"c",排除字符组是反义字符组的概念。
例如[^abc],表示是一个除"a"、"b"、"c"之外的任意一个字符。字符组的第一位放^(脱字符),表示求反的概念。
(3)常见的简写形式:
\d就是[0-9]。表示是一位数字。记忆方式:其英文是digit(数字)。
\D就是[^0-9]。表示除数字外的任意字符。
\w就是[0-9a-zA-Z_]。表示数字、大小写字母和下划线。记忆方式:w是word的简写,也称单词字符。
\W是[^0-9a-zA-Z_]。非单词字符。
\s是[ \t\v\n\r\f]。表示空白符,包括空格、水平制表符、垂直制表符、换行符、回车符、换页符。记忆方式:s是space character的首字母。
\S是[^ \t\v\n\r\f]。 非空白符。
.就是[^\n\r\u2028\u2029]。通配符,表示几乎任意字符。换行符、回车符、行分隔符和段分隔符除外。记忆方式:想想省略号...中的每个点,都可以理解成占位符,表示任何类似的东西。
如果要匹配任意字符怎么办?可以使用[\d\D]、[\w\W]、[\s\S]和[^]中任何的一个。
4.2 量词
量词也称为重复,表示重复多少次;例如:\d{3}表示连续匹配数字3次,即匹配三个连贯的数字。其中{}是闭区间。
(1)简写形式
{m,} 表示至少出现m次。
{m} 等价于{m,m},表示出现m次。
? 等价于{0,1},表示出现或者不出现。记忆方式:问号表示,有或没有
+ 等价于{1,},表示出现至少一次。
* 等价于{0,},表示出现任意次,有可能不出现。
(2)量词中存在的贪婪匹配和惰性匹配
例如:
其中正则\d{2,5},表示数字连续出现2到5次。会匹配2位、3位、4位、5位连续数字。但是其是贪婪的,它会尽可能多的匹配。你能给我6个,我就要5个。你能给我3个,我就要3个。反正只要在能力范围内,越多越好。
而惰性匹配,就是尽可能少的匹配;
其中\d{2,5}?表示,虽然2到5次都行,当2个就够的时候,就不在往下尝试了。
4.3 锚点
锚点即位置匹配,介绍锚点之前先了解下位置的概念。
(1)什么是位置
位置是相邻字符之间的位置。
(2)如何匹配位置
有8个锚字符可以去匹配位置
^ $ \b \B (?=p) (?!p) (?<=p) (?<!p)
其中
^和$
^(脱字符)匹配开头,在多行匹配中匹配行开头。$(美元符号)匹配结尾,在多行匹配中匹配行结尾。比如,把字符串的开头和结尾用“#”替换。
\b和\B
\b是单词边界,具体就是\w和\W之间的位置,也包括\w和^之间的位置,也包括\w和$之间的位置。
例如,将指定字符的边界替换为#
\B就是\b的反面的意思,非单词边界。例如在字符串中所有位置中,扣掉\b,剩下的都是\B的。
(?=p) 和 (?!p)
(?=p),学名叫零宽度正预测先行断言,其中p是一个子模式,即p前面的位置。例如(?=l),表示'l'字符前面的位置
而(?!p),学名叫零宽度负预测先行断言,就是(?=p)的反面意思,比如:
(3)案例分析
数字的千位分隔符表示法,如把“12345678”,变成“12,345,678”。
思路:找到对应的位置替换为“,”。
1、先弄出最后一个逗号
2、弄出所有逗号
因为逗号出现的位置,要求后面3个数字一组,也就是\d{3}至少出现一次。此时可以使用量词+:
3、要求匹配的位置不能是开头
(?<=p) 和(?<!p)
注:这两种模式javascript是不支持的,请在RegexBuddy工具中把语言切换到c#模式。
(?<=p) ,学名叫零宽度正回顾后发断言,表示p后面的这个位置
(?<!p),学名叫零宽度负回顾后发断言,表示不是p后面的位置,或者说是p后面的位置不能匹配;
4.4 多选分支
形式如:(p1|p2|p3),其中p1、p2、p3是子模式,用|(管道符)分隔,表示其中任何之一。
它也是惰性匹配,即找到一个匹配的就不继续尝试查找了。
4.5 括号的作用
a+匹配连续出现的“a”,而要匹配连续出现的“ab”时,需要使用(ab)+
多选分支结构(p1|p2)中,括号提供了子表达式的所有可能。如:
反向引用
在正则里引用之前出现的分组,即反向引用。如:当我们想用正则支持如下三种日期格式时:
以上正则虽然能匹配要求的情况,但也匹配了“2019-09/10”这样的数据,如果想要分割符前后一致,需要使用反向引用;
其中,\1表示引用之前的那个分组(-|\/|\.)。不管它匹配到什么(比如-),\1都匹配那个同样的具体某个字符。我们知道了\1的含义后,那么\2和\3的概念也就理解了,即分别指代第二个和第三个分组。
当括号存在嵌套时,以左括号为准,如
我们可以看看这个正则匹配模式:
第一个字符是数字,比如说1,
第二个字符是数字,比如说2,
第三个字符是数字,比如说3,
接下来的是\1,是第一个分组内容,那么看第一个开括号对应的分组是什么,是123,
接下来的是\2,找到第2个开括号,对应的分组,匹配的内容是1,
接下来的是\3,找到第3个开括号,对应的分组,匹配的内容是23,
最后的是\4,找到第4个开括号,对应的分组,匹配的内容是3。
引用不存在的分组
反向引用,是引用前面的分组,但我们在正则里引用了不存在的分组时,有些语言会报编译错误(引用了不存在的分组),如c#,但有些语言是支持的,如javascript,只是匹配反向引用的字符本身。例如\2,就匹配"\2"。注意"\2"表示对"2"进行了转意。大概如:
非捕获分组
之前文中出现的分组,都会捕获它们匹配到的数据,以便后续引用,因此也称他们是捕获型分组。如果只要括号最原始的功能,但不会引用它,即,既不在API里引用,也不在正则里反向引用。此时可以使用非捕获分组(?:p),注意和上文中的(?=p)进行区分。
4.6 回溯法原理
引擎会依次处理各个子表达式或组成元素,遇到需要在两个可能成功的选项进行选择的时候,它会选择其一,同时记住另一个,以备稍后可能的需要。如果正则表达式中余下的部分最终匹配失败,引擎会知道需要回溯到之前做出选择的地方,选择其他的备用分支继续尝试。这样,引擎最终会尝试表达式的所有可能途径(或者是匹配完成之前需要的所有途径)。
形象的说,回溯就像是在道路的每个分岔口留下一小堆面包屑。如果走了死路,就可以照原路返回,直到遇到面包屑标示的尚未尝试过的道路。如果那条路也走不通,你可以继续返回,找到下一堆面包屑,如此重复,直到找到出路,或者走完所有没有尝试过的路。
(1)没有回溯的匹配
假设我们的正则是ab{1,3}c,其可视化形式是
当目标字符串是"abbbc"时,就没有所谓的“回溯”。其匹配过程是
(2)有回溯的匹配
如果目标字符串是“abbc”,则会存在回溯。
图中第5步有红颜色,表示匹配不成功。此时b{1,3}已经匹配到了2个字符“b”,准备尝试第三个时,结果发现接下来的字符是“c”。那么就认为b{1,3}就已经匹配完毕。然后状态又回到之前的状态(即第6步,与第4步一样),最后再用子表达式c,去匹配字符“c”。当然,此时整个表达式匹配成功了。其中的第6步就是回溯。
再举个例子,正则是ab{1,3}bbc
目标字符串是"abbbc",匹配过程是:
其中第7步和第10步是回溯。第7步与第4步一样,此时b{1,3}匹配了两个"b",而第10步与第3步一样,此时b{1,3}只匹配了一个"b",这也是b{1,3}的最终匹配结果。
(3)回溯法总结
回溯法也称试探法,它的基本思想是:从问题的某一种状态(初始状态)出发,搜索从这种状态出发所能达到的所有“状态”,当一条路走到“尽头”的时候(不能再前进),再后退一步或若干
步,从另一种可能“状态”出发,继续搜索,直到所有的“路径”状态都尝试过。这种不断“前进”、不断“回溯”寻找解的方法,就称为“回溯法”。
(4)可能出现回溯的地方
贪婪量词
之前的例子都是贪婪量词相关的。比如b{1,3},因为其是贪婪的,尝试可能的顺序是从多往少的方向去尝试。首先会尝试"bbb",然后再看整个正则是否能匹配。不能匹配时,吐出一个"b",即在"bb"的基础上,再继续尝试。如果还不行,再吐出一个,再试。如果还不行呢?只能说明匹配失败了。
惰性量词
惰性量词就是在贪婪量词后面加个问号,表示尽可能少的匹配。虽然惰性量词不贪,但也会有回溯的现象。比如正则是
虽然惰性匹配不贪,但是为了整体匹配成,也只能给你多塞点。因此最后\d{1,3}?匹配的字符是"12",是两个数字,而不是一个。
五、实践
(1)试着用正则表达式匹配IP地址;
(2)验证用户密码,要求必须是大、小写字母、数字至少两种的结合;
(3)了解下正则表达式引发的血案,你知道问题出在哪儿吗? https://juejin.im/post/5b287ea6f265da596d04a324