整理了一些关于正则的知识,不是原创
基础
字符组[]
正则表达式[\w]+,\w+,[\w+] 三者有何区别:
[\w]+和\w+没有区别,都是匹配数字和字母下划线的多个字符;
[\w+]表示匹配数字、字母、下划线和加号本身字符;
^
[^]
|
[|] [|]
中文
[\u4e00-\u9fa5]
转义符 \
元字符(Metacharacter)是拥有特殊含义的字符:
元字符 描述
. 查找单个字符,除了换行和行结束符。
w 查找单词字符。
W 查找非单词字符。
d 查找数字。
D 查找非数字字符。
s 查找空白字符。
S 查找非空白字符。
b 查找位于单词的开头或结尾的匹配。
B 查找不处在单词的开头或结尾的匹配。
查找 NUL 字符。
n 查找换行符。
f 查找换页符。
r 查找回车符。
t 查找制表符。
v 查找垂直制表符。
xxx 查找以八进制数 xxx 规定的字符。
xdd 查找以十六进制数 dd 规定的字符。
uxxxx 查找以十六进制数 xxxx 规定的 Unicode 字符。
\d 代表[0-9] \D 代表[^0-9]
\w 代表[0-9a-zA-Z_] \W 代表[^0-9a-zA-Z_]
\s 代表空白符 \S 代表非空白符
\n 换行 \t 制表符(Tab键)
\f 换页符 \v 垂直制表符
[\b] 退格 注意: \b是匹配匹配一个词语的边界
量词
① {n,m} 最小值—最大值
② {n,} 至少n次或者更多次
③ {n} 匹配恰好n次
//贪婪模式
④ ? 匹配等价于{0,1}
⑤ + >=1 等价于{1,}
⑥ >=0 等价于{0,}
//非贪婪
+?
?
表达式修饰符
i 不区分(ignore)大小写;
例如: /abc/i 可以匹配 abc、aBC、Abc
g 全局(global)匹配
如果不带g,正则过程中字符串从左到右匹配,找到第一个符合条件的即匹配成功,返回
如果带g,则字符串从左到右,找到每个符合条件的都记录下来,知道字符串结尾位置
可以得到很多结果
例如:
var str = ‘aaaaaaaa’
var reg1 = /a/; str.match(reg1) // 结果为:[“a”, index: 0, input: “aaaaaaaa”]
var reg2 = /a/g; str.match(reg2) // 结果为:[“a”, “a”, “a”, “a”, “a”, “a”, “a”, “a”]
/m (多行查找) 多行模式
/s 单行模式
单行模式中;$表示段尾 ,^表示段首
多行模式中:$表示行尾(\n),^表示每行首
NFA 是 表达式主导引擎, NFA又基本上可以分为传统型NFA和POSIX NFA
DFA 则是 文本主导引擎
DFA Deterministic finite automaton 确定型有穷自动机
NFA Non-deterministic finite automaton 非确定型有穷自动机
DFA引擎因为不需要回溯,所以匹配快速,但不支持捕获组,所以也就不支持反向引用和$number这种引用方式,目前使用DFA引擎的语言和工具主要有awk、egrep 和 lex。
POSIX NFA主要指符合POSIX标准的NFA引擎,它的特点主要是提供longest-leftmost匹配,也就是在找到最左侧最长匹配之前,它将继续回溯。同DFA一样,非贪婪模式或者说忽略优先量词对于POSIX NFA同样是没有意义的。
大多数语言和工具使用的是传统型的NFA引擎
js是传统型NFA
支持
()
\1 \2
$1 $2
(?:)非捕获
1.捕获组、反向引用和$number引用方式;
2.环视(Lookaround,(?<=…)、(?<!…)、(?=…)、(?!…)),或者有的有文章叫做预搜索;
前瞻 (?=) 也叫正向零宽先行断言 后面有这个
反向前瞻(?!=) 也叫负向零宽先行断言 后面没有这个
后顾(?<=) 正向零宽后行断言 前面有
反向后顾(?<!) 前面没有
(js不支持后顾)
3.忽略优化量词(??、*?、+?、{m,n}?、{m,}?),或者有的文章叫做非贪婪模式; 匹配优先 忽略优先(贪婪,非贪婪)
4.占有优先量词(?+、*+、++、{m,n}+、{m,}+,目前仅Java和PCRE支持),固化分组(?>…)。
有些流派并不支持占有优先量词和固化分组,比如JavaScript,这是我们就可以用肯定环视来模拟固化分组:??
(?>…) 固化分组(成功匹配后,回簌时不会考虑这个匹配的字符) 效率高
占有字符和零宽度
正则表达式匹配过程中,如果子表达式匹配到的是字符内容,而非位置,并被保存到最终的匹配结果中,那么就认为这个子表达式是占有字符的;
如果子表达式匹配的仅仅是位置,或者匹配的内容并不保存到最终的匹配结果中,那么就认为这个子表达式是零宽度的
占有字符是互斥的,
零宽度是非互斥的。
也就是一个字符,同一时间只能由一个子表达式匹配,
而一个位置,却可以同时由多个零宽度的子表达式匹配。
正则的匹配过程,通常情况下都是由一个子表达式(可能为一个普通字符、元字符或元字符序列组成)取得控制权,从字符串的某一位置开始尝试匹配,一个子表达式开始尝试匹配的位置,是从前一子表达匹配成功的结束位置开始的。如正则表达式:
(子表达式一)(子表达式二)
假设(子表达式一)为零宽度表达式,由于它匹配开始和结束的位置是同一个,如位置0,那么(子表达式二)是从位置0开始尝试匹配的。
假设(子表达式一)为占有字符的表达式,由于它匹配开始和结束的位置不是同一个,如匹配成功开始于位置0,结束于位置2,那么(子表达式二)是从位置2开始尝试匹配的。
javascript只支持零宽先行断言,而零宽先行断言又可以分为正向零宽先行断言,和负向零宽先行断言。
代码实例如下:
实例代码一:
var str=”abZW863”;
var reg=/ab(?=[A-Z])/;
console.log(str.match(reg));
在以上代码中,正则表达式的语义是:匹配后面跟随任意一个大写字母的字符串”ab”。
最终匹配结果是”ab”,
因为零宽断言”(?=[A-Z])”并不匹配任何字符,只是用来规定当前位置的后面必须是一个大写字母。
实例代码二
var str=”Iraq”;
var str=”qoph”;
var regex=/q[^u]/
这里 Irap 不能匹配成功,[^u]需要一个字符,一个不是u的字符,
正则表达式:abc
匹配过程:
首先由字符“a”取得控制权,从位置0开始匹配,由“a”来匹配“a”,匹配成功,控制权交给字符“b”;由于“a”已被“a”匹配,所以“b”从位置1开始尝试匹配,由“b”来匹配“b”,匹配成功,控制权交给“c”;由“c”来匹配“c”,匹配成功。
此时正则表达式匹配完成,报告匹配成功。匹配结果为“abc”,开始位置为0,结束位置为3。
正则表达式:^(?=[a-z])[a-z0-9]+$
元字符“^”和“$”匹配的只是位置,顺序环视“(?=[a-z])”只进行匹配,并不占有字符,也不将匹配的内容保存到最终的匹配结果,所以都是零宽度的。
这个正则的意义就是匹配由字母和数字组成的,第一个字符是字母的字符串。
匹配过程:
首先由元字符“^”取得控制权,从位置0开始匹配,“^”匹配的就是开始位置“位置0”,匹配成功,控制权交给顺序环视“(?=[a-z])”;
“(?=[a-z])”要求它所在位置右侧必须是字母才能匹配成功,零宽度的子表达式之间是不互斥的,即同一个位置可以同时由多个零宽度子表达式匹配,所以它也是从位置0尝试进行匹配,位置0的右侧是字符“a”,符合要求,匹配成功,控制权交给“[a-z0-9]+”;
因为“(?=[a-z])”只进行匹配,并不将匹配到的内容保存到最后结果,并且“(?=[a-z])”匹配成功的位置是位置0,所以“[a-z0-9]+”也是从位置0开始尝试匹配的,“[a-z0-9]+”首先尝试匹配“a”,匹配成功,继续尝试匹配,可以成功匹配接下来的“1”和“2”,此时已经匹配到位置3,位置3的右侧已没有字符,这时会把控制权交给“$”;
元字符“$”从位置3开始尝试匹配,它匹配的是结束位置,也就是“位置3”,匹配成功。
此时正则表达式匹配完成,报告匹配成功。匹配结果为“a12”,开始位置为0,结束位置为3。其中“^”匹配位置0,“(?=[a-z])”匹配位置0,“[a-z0-9]+”匹配字符串“a12”,“$”匹配位置3。
校验:
var userName_regex = /^((([\u4E00-\u9FA5\uf900-\ufa2d])([\u4E00-\u9FA5\uf900-\ufa2d]|_(?!_|-|·)|-(?!_|-|·)|·(?!_|-|·)){0,48}([\u4E00-\u9FA5\uf900-\ufa2d])$)|(([a-zA-Z])([a-zA-Z]|\s(?!_|-|·|\s)|_(?!_|-|·|\s)|-(?!_|-|·|\s)|·(?!_|-|·|\s)){0,48})([a-zA-Z])$)$/;
var userName_regex =
/^((([\u4E00-\u9FA5\uf900-\ufa2d])
([\u4E00-\u9FA5\uf900-\ufa2d] | _(?!_|-|·)|-(?!_|-|·)|·(?!_|-|·)){0,48} ([\u4E00-\u9FA5\uf900-\ufa2d]) $) |
(([a-zA-Z])([a-zA-Z]|\s(?!_|-|·|\s)|_(?!_|-|·|\s)|-(?!_|-|·|\s)|·(?!_|-|·|\s)){0,48})([a-zA-Z])$)$/
;
var s = ‘by Jeffrey Friedl’
var s = ‘by Thomas Jefferson’
var r=/(?=Jeffrey)Jeff/
var s=’123456789’;
任务 改造成’123,456,789’
var r=/(?<=\d>)(?=\d\d\d)/,/g
||||| 效率很低
什么是贪婪与非贪婪模式
正则表达式:ab?c
匹配优先
忽略优先
量词“?”属于匹配优先量词,在可匹配可不匹配时,会先选择尝试匹配,只有这种选择会使整个表达式无法匹配成功时,才会尝试让出匹配到的内容。这里的量词“?”是用来修饰字符“b”的,所以“b?”是一个整体。
匹配过程:
首先由字符“a”取得控制权,从位置0开始匹配,由“a”来匹配“a”,匹配成功,控制权交给字符“b?”;由于“?”是匹配优先量词,所以会先尝试进行匹配,由“b?”来匹配“b”,匹配成功,控制权交给“c”,同时记录一个备选状态;由“c”来匹配“c”,匹配成功。记录的备选状态丢弃。
此时正则表达式匹配完成,报告匹配成功。匹配结果为“abc”,开始位置为0,结束位置为3。
正则表达式:ab?c
匹配过程:
首先由字符“a”取得控制权,从位置0开始匹配,由“a”来匹配“a”,匹配成功,控制权交给字符“b?”;先尝试进行匹配,由“b?”来匹配“c”,同时记录一个备选状态,匹配失败,此时进行回溯,找到备选状态,“b?”忽略匹配,让出控制权,把控制权交给“c”;由“c”来匹配“c”,匹配成功。
量词“??”属于忽略优先量词,在可匹配可不匹配时,会先选择不匹配,只有这种选择会使整个表达式无法匹配成功时,才会尝试进行匹配。这里的量词“??”是用来修饰字符“b”的,所以“b??”是一个整体。
匹配过程:
首先由字符“a”取得控制权,从位置0开始匹配,由“a”来匹配“a”,匹配成功,控制权交给字符“b??”;先尝试忽略匹配,即“b??”不进行匹配,同时记录一个备选状态,控制权交给“c”;由“c”来匹配“b”,匹配失败,此时进行回溯,找到记录的备选状态,“b??”尝试匹配,即“b??”来匹配“b”,匹配成功,把控制权交给“c”;由“c”来匹配“c”,匹配成功。
此时正则表达式匹配完成,报告匹配成功。匹配结果为“abc”,开始位置为0,结束位置为3。其中“b??”匹配字符“b”。
先看一个例子
举例:
源字符串:aa
正则表达式一:
匹配结果一:
正则表达式二:
匹配结果二:
在“整个表达式匹配成功”的前提下,贪婪模式才真正的影响着子表达式的匹配行为,如果整个表达式匹配失败,贪婪模式只会影响匹配过程,对匹配结果的影响无从谈起。
源字符串:”Regex”
正则表达式:”.“
贪婪
来看一下匹配过程。首先由第一个“””取得控制权,匹配位置0位的“””,匹配成功,控制权交给“.”。
“.”取得控制权后,由于“”是匹配优先量词,在可匹配可不匹配的情况下,优先尝试匹配。从位置1处的“R”开始尝试匹配,匹配成功,继续向右匹配,匹配位置2处的“e”,匹配成功,继续向右匹配,直到匹配到结尾的“””,匹配成功,由于此时已匹配到字符串的结尾,所以“.*”结束匹配,将控制权交给正则表达式最后的“””。
“””取得控制权后,由于已经在字符串结束位置,匹配失败,向前查找可供回溯的状态,控制权交给“.”,由“.”让出一个字符,也就是字符串结尾处的“””,再把控制权交给正则表达式最后的“””,由“””匹配字符串结尾处的“””,匹配成功。
此时整个正则表达式匹配成功,其中“.*”匹配的内容为“Regex”,匹配过程中进行了一次回溯。
正则表达式:”.*?”
“.?”取得控制权后,由于“?”是忽略优先量词,在可匹配可不匹配的情况下,优先尝试不匹配,由于“*”等价于“{0,}”,所以在忽略优先的情况下,可以不匹配任何内容。从位置1处尝试忽略匹配,也就是不匹配任何内容,将控制权交给正则表达式最后的“””。
“””取得控制权后,从位置1处尝试匹配,由“””匹配位置1处的“R”,匹配失败,向前查找可供回溯的状态,控制权交给“.?”,由“.?”吃进一个字符,匹配位置1处的“R”,再把控制权交给正则表达式最后的“””。
“””取得控制权后,从位置2处尝试匹配,由“””匹配位置1处的“e”,匹配失败,向前查找可供回溯的状态,重复以上过程,直到由“.*?”匹配到“x”为止,再把控制权交给正则表达式最后的“””。
“””取得控制权后,从位置6处尝试匹配,由“””匹配字符串最后的“””,匹配成功。
此时整个正则表达式匹配成功,其中“.*?”匹配的内容为“Regex”,匹配过程中进行了五次回溯。
贪婪模式还有一点优势,就是在匹配失败时,贪婪模式可以更快速的报告失败,从而提升匹配效率
总结
能达到同样匹配结果的贪婪与非贪婪模式,通常是贪婪模式的匹配效率较高。
所有的非贪婪模式,都可以通过修改量词修饰的子表达式,转换为贪婪模式。
贪婪模式可以与固化分组结合,提升匹配效率,而非贪婪模式却不可以。
固话分组
“(?>[^”]*)”