正则知识整理

整理了一些关于正则的知识,不是原创

基础

字符组[]

正则表达式[\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的字符,

  1. 源字符串:abc

正则表达式:abc

匹配过程:

首先由字符“a”取得控制权,从位置0开始匹配,由“a”来匹配“a”,匹配成功,控制权交给字符“b”;由于“a”已被“a”匹配,所以“b”从位置1开始尝试匹配,由“b”来匹配“b”,匹配成功,控制权交给“c”;由“c”来匹配“c”,匹配成功。

此时正则表达式匹配完成,报告匹配成功。匹配结果为“abc”,开始位置为0,结束位置为3。

  1. 源字符串:a12

正则表达式:^(?=[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。

校验:

  1. 汉字之间不允许有空格;英文名字母中间只允许有1个空格;汉字与符号之间,字母与符号之间不允许有空格。
  2. “•”或“_”或“-”只能出现在两个汉字或两个字母之间,两个汉字或两个字母之间只允许有1个符号。
  3. 仅可以为汉字、字母以及汉字与符号的组合、字母与符号的组合,符号仅允许下列符号“•”( GB13000编码为00B7,GB18030编码为A1A4)或“” 或“-”或空格(“” 或“-”或空格仅允许半角模式)。
  4. 必须大于等于2个字符,小于等于50个字符 (一个字符就是一个字、字母)
 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

||||| 效率很低 

什么是贪婪与非贪婪模式

  1. 源字符串:abc

正则表达式:ab?c

匹配优先
忽略优先

量词“?”属于匹配优先量词,在可匹配可不匹配时,会先选择尝试匹配,只有这种选择会使整个表达式无法匹配成功时,才会尝试让出匹配到的内容。这里的量词“?”是用来修饰字符“b”的,所以“b?”是一个整体。

匹配过程:

首先由字符“a”取得控制权,从位置0开始匹配,由“a”来匹配“a”,匹配成功,控制权交给字符“b?”;由于“?”是匹配优先量词,所以会先尝试进行匹配,由“b?”来匹配“b”,匹配成功,控制权交给“c”,同时记录一个备选状态;由“c”来匹配“c”,匹配成功。记录的备选状态丢弃。

此时正则表达式匹配完成,报告匹配成功。匹配结果为“abc”,开始位置为0,结束位置为3。

  1. 源字符串:ac

正则表达式:ab?c

匹配过程:

首先由字符“a”取得控制权,从位置0开始匹配,由“a”来匹配“a”,匹配成功,控制权交给字符“b?”;先尝试进行匹配,由“b?”来匹配“c”,同时记录一个备选状态,匹配失败,此时进行回溯,找到备选状态,“b?”忽略匹配,让出控制权,把控制权交给“c”;由“c”来匹配“c”,匹配成功。

  1. 源字符串:abc
    正则表达式:ab??c

量词“??”属于忽略优先量词,在可匹配可不匹配时,会先选择不匹配,只有这种选择会使整个表达式无法匹配成功时,才会尝试进行匹配。这里的量词“??”是用来修饰字符“b”的,所以“b??”是一个整体。

匹配过程:

首先由字符“a”取得控制权,从位置0开始匹配,由“a”来匹配“a”,匹配成功,控制权交给字符“b??”;先尝试忽略匹配,即“b??”不进行匹配,同时记录一个备选状态,控制权交给“c”;由“c”来匹配“b”,匹配失败,此时进行回溯,找到记录的备选状态,“b??”尝试匹配,即“b??”来匹配“b”,匹配成功,把控制权交给“c”;由“c”来匹配“c”,匹配成功。

此时正则表达式匹配完成,报告匹配成功。匹配结果为“abc”,开始位置为0,结束位置为3。其中“b??”匹配字符“b”。

先看一个例子

举例:

源字符串:aa

test1
bb
test2
cc

正则表达式一:

.*

匹配结果一:

test1
bb
test2

正则表达式二:

.*?

匹配结果二:

test1
(这里指的是一次匹配结果,所以没包括
test2

在“整个表达式匹配成功”的前提下,贪婪模式才真正的影响着子表达式的匹配行为,如果整个表达式匹配失败,贪婪模式只会影响匹配过程,对匹配结果的影响无从谈起。

源字符串:”Regex”

正则表达式:”.
贪婪
来看一下匹配过程。首先由第一个“””取得控制权,匹配位置0位的“””,匹配成功,控制权交给“.
”。

“.”取得控制权后,由于“”是匹配优先量词,在可匹配可不匹配的情况下,优先尝试匹配。从位置1处的“R”开始尝试匹配,匹配成功,继续向右匹配,匹配位置2处的“e”,匹配成功,继续向右匹配,直到匹配到结尾的“””,匹配成功,由于此时已匹配到字符串的结尾,所以“.*”结束匹配,将控制权交给正则表达式最后的“””。

“””取得控制权后,由于已经在字符串结束位置,匹配失败,向前查找可供回溯的状态,控制权交给“.”,由“.”让出一个字符,也就是字符串结尾处的“””,再把控制权交给正则表达式最后的“””,由“””匹配字符串结尾处的“””,匹配成功。

此时整个正则表达式匹配成功,其中“.*”匹配的内容为“Regex”,匹配过程中进行了一次回溯。

正则表达式:”.*?”

“.?”取得控制权后,由于“?”是忽略优先量词,在可匹配可不匹配的情况下,优先尝试不匹配,由于“*”等价于“{0,}”,所以在忽略优先的情况下,可以不匹配任何内容。从位置1处尝试忽略匹配,也就是不匹配任何内容,将控制权交给正则表达式最后的“””。

“””取得控制权后,从位置1处尝试匹配,由“””匹配位置1处的“R”,匹配失败,向前查找可供回溯的状态,控制权交给“.?”,由“.?”吃进一个字符,匹配位置1处的“R”,再把控制权交给正则表达式最后的“””。

“””取得控制权后,从位置2处尝试匹配,由“””匹配位置1处的“e”,匹配失败,向前查找可供回溯的状态,重复以上过程,直到由“.*?”匹配到“x”为止,再把控制权交给正则表达式最后的“””。

“””取得控制权后,从位置6处尝试匹配,由“””匹配字符串最后的“””,匹配成功。

此时整个正则表达式匹配成功,其中“.*?”匹配的内容为“Regex”,匹配过程中进行了五次回溯。

贪婪模式还有一点优势,就是在匹配失败时,贪婪模式可以更快速的报告失败,从而提升匹配效率

总结

能达到同样匹配结果的贪婪与非贪婪模式,通常是贪婪模式的匹配效率较高。

所有的非贪婪模式,都可以通过修改量词修饰的子表达式,转换为贪婪模式。

贪婪模式可以与固化分组结合,提升匹配效率,而非贪婪模式却不可以。

固话分组
“(?>[^”]*)”