正则表达式匹配规则

本文介绍的并不是正则表达式的通配符含义,或者一些正则的书写技巧
而是介绍正则匹配的流程,正则进行匹配的规则

1.首先字符串中的每一个字符都有位置

例如字符串:abcde
这个时候位置关系如下

     a      b      c      d      e
  ↑      ↑      ↑      ↑      ↑      ↑
 0位置  1位置  2位置   3位置  4位置  5位置

2.正则表达式有一个占位的概念

分为两种
①是普通匹配,普通匹配占宽度,匹配的是字符
②是断言,断言不占宽度,匹配的是某一个位置,所以断言也被叫做零宽度断言。

举个例子

例如表达式:/\w(?=\d)/
首先表达式拆解为 \w 与 (?=\d) 两个部分,
表达式 \w 匹配一个字符,占用一个宽度,(?=\d) 表示一个数字的位置
整个正则表达式连接起来就是,首先匹配一个字母或者数字 \w,匹配的这个 \w 后面必须紧跟一个数字 (?=\d)
这里的 \w 为普通匹配,占用宽度,而 (?=\d) 为断言,并不占用宽度,只是规定 \w 后面必须含有一个数字

看一下使用上面这个表达书进行匹配的结果

('abc123').match(/\w(?=\d)/);
// 匹配到的结果为 ['c', index: 2, input: 'abc123']

断言由于不占宽度所以match的结果只有字符'c'

3.正则表达式的控制权概念

联系第 1 点和第 2 点
例如我们有一个正则表达式,和一个需要进行匹配的字符串

let reg = /bc/g;
let str = 'abcdabc';

首先对字符串位置做标记,方便我们观察

      a      b      c      d      a      b      c
  ↑      ↑      ↑      ↑      ↑      ↑      ↑      ↑
0位置   1位置  2位置   3位置  4位置  5位置   6位置  7位置

同样的,我们对正则表达式 /bc/g 进行拆解为 b 与 c 两部分

匹配的过程如下:

  1. 整个正则表达式的起始匹配字符为 b,整个正则控制权在 b 手中,首先从 0 位置开始匹配字符串 abcdabc
  2. 0 位置之后字符为 a,匹配失败,匹配位置由 0 位置转移到 1 位置,正则控制权回到整个正则表达式的最前面,所以这个时候控制权依旧在 b 手中(其实这里还牵扯到回溯,这里暂时先不做讨论)
  3. 匹配位置转移到 1 之后,从 1 位置开始匹配,字符串 1 位置之后的字符为 b,此时正则的控制权也是在 b 字符上,匹配成功,匹配的位置从 1 变为 2 ,由于正则表达式中的 b 字符没有做量词修饰,即它表示只占用 1 个位置的宽度,所以 b 的控制结束,控制权移交给正则的下一个匹配字符 c
  4. 从 2 位置继续匹配,字符串 2 位置之后的字符为 c,此时正则的控制权也正好在字符 c 上,匹配成功,匹配位置从 2 变为 3,由于 c 没有做任何量词的修饰,表示他只占用 1 个宽度,所以 c 的控制结束,控制权继续移交给正则的下一个匹配字符
  5. 匹配字符 c 之后已经没有其他的匹配字符了,整个正则表达式结束,此时字符串的匹配位置依旧是上一步结束时候的 3 位置
  6. 由于正则表达式声明为 g 全局匹配,所以整个正则表达式将从头开始,控制权移交给整个正则表达式的起始字符 b
  7. 正则表达式控制权在 b,匹配位置为之前匹配成功的结束位置 3,字符串 3 位置之后的字符为 d,表达式 b 匹配字符 d 失败,控制权依旧在起始位置 b,字符串匹配位置由 3 变为 4
  8. 正则表达式控制权在 b,匹配位置为 4,字符串 4 位置时候的字符为 a,表达式 b 匹配字符 a 失败,控制权依旧在起始位置 b,字符串匹配位置由 4 变为 5
  9. 正则表达式控制权在 b,匹配位置为 5,字符串 5 位置时候的字符为 b,匹配成功,匹配的位置从 5 变为 6 位置,由于正则表达式中的 b 字符没有做量词修饰,即它表示只占用 1 个位置的宽度,所以 b 的控制结束,控制权移交给正则的下一个匹配字符c
  10. 正则表达式控制权在 c,匹配位置为 6,字符串 6 位置时候的字符为 c,匹配成功,匹配的位置从 6 变为 7 位置,由于正则表达式中的 c 字符没有做量词修饰,即它表示只占用 1 个位置的宽度,所以 c 的控制结束,控制权继续向后移交
  11. 正则表达式结束,由于它是全局匹配,控制权再次回到 b,但是由于这个时候所匹配的字符串已经结束了,所以整个匹配过程结束

4.回溯

①当正则匹配不成功的时候,就会尝试进行回溯
②回溯成功与否取决于是否有可回溯的位置
③若没有会回溯位置,则整个正则表达式匹配失败,控制权交还给表达式的起始位置
④正则规则中使用量词修饰,或者使用|的时候,所匹配的位置为可回溯位置

依旧是一个简单的例子

    let reg = /ab{1,3}bbc/;
    let str = 'abbbc';
      a      b      b      b      c
  ↑      ↑      ↑      ↑      ↑      ↑
0位置   1位置  2位置   3位置  4位置  5位置

依旧对正则进行拆解,拆解成 a,b{1,3},b,b,c 五部分
依旧按照之前的规则进行匹配

  1. 控制权 a,起始位置 0(整个正则表达式的最开始位置),匹配 a 成功,匹配位置变为 1,a 宽度为 1,控制权移交给 b{1,3}
 字符串: a
         ↑
 正  则: a
  1. 控制权 b{1,3},匹配位置 1,匹配 b 成功,匹配位置变为 2(第 1 个可回溯位置),b{1,3} 宽度 1-3 之间,默认为贪婪模式,控制权依旧在 b{1,3}
 字符串: a       b
         ↑       ↑
 正  则: a    b{1,3}
  1. 控制权 b{1,3},匹配位置 2,匹配 b 成功,匹配位置变为 3(第 2 个可回溯位置),b{1,3} 宽度1-3之间,默认为贪婪模式,控制权依旧在 b{1,3}
 字符串: a      bb
         ↑       ↑
 正  则: a    b{1,3}
  1. 控制权 b{1,3},匹配位置 3,匹配 b 成功,匹配位置变为 4,b{1,3} 宽度 1-3 之间,已经达到最大宽度,结束匹配,控制权移交给 b
 字符串: a      bbb
         ↑       ↑
 正  则: a    b{1,3}
  1. 控制权 b(前一个),匹配位置 4,匹配 c 失败,尝试进行回溯,最近的一个可回溯位置为 3 位置,此时 b{1,3} 这个时候只匹配头两个 bb 字符,从 3 位置开始,b(前一个)匹配第三个 b 字符,回溯成功,b 宽度为 1,控制权移交给 b(后一个),匹配位置变为4
 字符串: a      bb       b
         ↑       ↑       ↑
 正  则: a    b{1,3}     b
  1. 控制权 b(后一个),匹配位置 4,匹配 c 失败,尝试进行回溯,上一个可回溯位置为 2 位置,此时 b{1,3} 这个时候只匹配 b 一个字符,控制权移交给 b (前一个),从 2 位置匹配 b (前一个)成功,匹配位置变为 3,控制权移交给 b(后一个),从 3 位置匹配 b(后一个)成功,匹配位置变为 4,控制权移交给 c
 字符串: a       b       b      b
         ↑       ↑       ↑      ↑
 正  则: a    b{1,3}     b      b
  1. 控制权 c,匹配位置 4,匹配 c 成功,匹配位置变为 5
 字符串: a       b       b      b      c
         ↑       ↑       ↑      ↑      ↑
 正  则: a    b{1,3}     b      b      c
  1. 字符串结束,整个匹配过程结束

回溯总结

①正则匹配规则 a, b{1,3}, b, b, c 分别匹配到了字符 a, b, b, b, c
②整个过程中由于 b{1,3} 存在可回溯位置,正则默认匹配规则为贪婪模式,b{1,3} 首先尽可能多的匹配,直到无法继续匹配的时候将控制权移交给下一个匹配字符
③当之后的匹配字符匹配失败的时候,正则表达式尝试从可回溯位置开始进行匹配,如果匹配依旧失败的话,再往前找上一个可回溯位置,直到表达式匹配成功
④如果已经没有任何可回溯位置能满足表达式,则整个表达式匹配失败,它将从上次匹配字符串的开始位置的下一个位置再次尝试匹配

5.贪婪模式和非贪婪模式

正则默认为贪婪模式,
贪婪模式为尽可能多的匹配,但是非贪婪莫模式不能只解释为尽可能少的匹配

let reg = /\w+?/;
let str = 'abcd1234efgh5678';
str.match(reg);
// 结果为['a']

这个时候确实可以理解为尽可能少的匹配

再看一个例子

reg = /\w+?\d/;
str.match(reg);
// 结果为['abcd1']

这个时候如果按照尽可能少的匹配的原则,匹配到的应该是['d1']
所以不能单纯的理解为尽可能少的匹配

6.其他一些匹配回溯的例子

let reg = /[a-z]{1,5}1/;
let str = 'abcdef1ghijkl';

表达式拆解为 [a-z]{1,5} 与 1 两部分

  a   b   c   d   e   f   1   g   h   i   j   k   l
↑   ↑   ↑   ↑   ↑   ↑   ↑   ↑   ↑   ↑   ↑   ↑   ↑   ↑
0   1   2   3   4   5   6   7   8   9  10   11  12  13

从0位置开始匹配

  1. [a-z]{1,5} 匹配 abcde 控制权移交给 1
 字符串:    abcde         f
              ↑           ×
 正  则: [a-z]{1,5}       1
  1. 1 匹配f失败,尝试回溯,[a-z]{1,5} 匹配 abcd ,控制权交给 1
 字符串:    abcd         e
              ↑          ×
 正  则: [a-z]{1,5}      1
  1. 1 匹配 e 失败,尝试回溯,[a-z]{1,5} 匹配 abc,控制权交给 1
 字符串:     abc         d
              ↑          ×
 正  则: [a-z]{1,5}      1
  1. 1匹配d失败,尝试回溯,[a-z]{1,5}匹配ab,控制权交给1
 字符串:     ab         c
              ↑         ×
 正  则: [a-z]{1,5}     1
  1. 1匹配c失败,尝试回溯,[a-z]{1,5}匹配a,控制权交给1
 字符串:      a         b
              ↑         ×
 正  则: [a-z]{1,5}     1
  1. 1匹配b失败,[a-z]{1,5}无可回溯位置,[a-z]{1,5}前面的表达式也没有可回溯位置,匹配失败,即从字符串0位置开始匹配失败

从1位置开始匹配

  1. [a-z]{1,5} 匹配 bcdef 控制权移交给 1
 字符串:  a      bcdef
                   ↑
 正  则:      [a-z]{1,5}
  1. 1 匹配 1 成功,控制权继续向后移交
 字符串:  a      bcdef       1
                   ↑         ↑
 正  则:      [a-z]{1,5}     1
  1. 个表达式结束,并且未声明为全局匹配,整个匹配过程结束

7.回溯例子

let str = 'abbbbbc';
let reg = /ab{1,3}b{1,2}bc/;
表达式                字符串

a                     a
ab{1,3}               ab (a, b{1,3}分别匹配a, b)
ab{1,3}               abb (a, b{1,3}分别匹配a, bb)
ab{1,3}               abbb (a, b{1,3}分别匹配a, bbb)
ab{1,3}b{1,2}         abbbb (a, b{1,3}, b{1,2}分别匹配a, bbb, b)
ab{1,3}b{1,2}         abbbbb (a, b{1,3}, b{1,2}分别匹配a, bbb, bb)
ab{1,3}b{1,2}b        abbbbbc (a, b{1,3}, b{1,2}, b分别匹配a, bbb, bb, c, 匹配失败尝试进行回溯)
ab{1,3}b{1,2}b        abbbbb (a, b{1,3}, b{1,2}, b分别匹配a, bbb, b, b)
ab{1,3}b{1,2}bc       abbbbbc (a, b{1,3}, b{1,2}, b, c分别匹配a, bbb, b, b, c)
最后编辑于
©著作权归作者所有,转载或内容合作请联系作者
  • 序言:七十年代末,一起剥皮案震惊了整个滨河市,随后出现的几起案子,更是在滨河造成了极大的恐慌,老刑警刘岩,带你破解...
    沈念sama阅读 204,530评论 6 478
  • 序言:滨河连续发生了三起死亡事件,死亡现场离奇诡异,居然都是意外死亡,警方通过查阅死者的电脑和手机,发现死者居然都...
    沈念sama阅读 86,403评论 2 381
  • 文/潘晓璐 我一进店门,熙熙楼的掌柜王于贵愁眉苦脸地迎上来,“玉大人,你说我怎么就摊上这事。” “怎么了?”我有些...
    开封第一讲书人阅读 151,120评论 0 337
  • 文/不坏的土叔 我叫张陵,是天一观的道长。 经常有香客问我,道长,这世上最难降的妖魔是什么? 我笑而不...
    开封第一讲书人阅读 54,770评论 1 277
  • 正文 为了忘掉前任,我火速办了婚礼,结果婚礼上,老公的妹妹穿的比我还像新娘。我一直安慰自己,他们只是感情好,可当我...
    茶点故事阅读 63,758评论 5 367
  • 文/花漫 我一把揭开白布。 她就那样静静地躺着,像睡着了一般。 火红的嫁衣衬着肌肤如雪。 梳的纹丝不乱的头发上,一...
    开封第一讲书人阅读 48,649评论 1 281
  • 那天,我揣着相机与录音,去河边找鬼。 笑死,一个胖子当着我的面吹牛,可吹牛的内容都是我干的。 我是一名探鬼主播,决...
    沈念sama阅读 38,021评论 3 398
  • 文/苍兰香墨 我猛地睁开眼,长吁一口气:“原来是场噩梦啊……” “哼!你这毒妇竟也来了?” 一声冷哼从身侧响起,我...
    开封第一讲书人阅读 36,675评论 0 258
  • 序言:老挝万荣一对情侣失踪,失踪者是张志新(化名)和其女友刘颖,没想到半个月后,有当地人在树林里发现了一具尸体,经...
    沈念sama阅读 40,931评论 1 299
  • 正文 独居荒郊野岭守林人离奇死亡,尸身上长有42处带血的脓包…… 初始之章·张勋 以下内容为张勋视角 年9月15日...
    茶点故事阅读 35,659评论 2 321
  • 正文 我和宋清朗相恋三年,在试婚纱的时候发现自己被绿了。 大学时的朋友给我发了我未婚夫和他白月光在一起吃饭的照片。...
    茶点故事阅读 37,751评论 1 330
  • 序言:一个原本活蹦乱跳的男人离奇死亡,死状恐怖,灵堂内的尸体忽然破棺而出,到底是诈尸还是另有隐情,我是刑警宁泽,带...
    沈念sama阅读 33,410评论 4 321
  • 正文 年R本政府宣布,位于F岛的核电站,受9级特大地震影响,放射性物质发生泄漏。R本人自食恶果不足惜,却给世界环境...
    茶点故事阅读 39,004评论 3 307
  • 文/蒙蒙 一、第九天 我趴在偏房一处隐蔽的房顶上张望。 院中可真热闹,春花似锦、人声如沸。这庄子的主人今日做“春日...
    开封第一讲书人阅读 29,969评论 0 19
  • 文/苍兰香墨 我抬头看了看天上的太阳。三九已至,却和暖如春,着一层夹袄步出监牢的瞬间,已是汗流浃背。 一阵脚步声响...
    开封第一讲书人阅读 31,203评论 1 260
  • 我被黑心中介骗来泰国打工, 没想到刚下飞机就差点儿被人妖公主榨干…… 1. 我叫王不留,地道东北人。 一个月前我还...
    沈念sama阅读 45,042评论 2 350
  • 正文 我出身青楼,却偏偏与公主长得像,于是被迫代替她去往敌国和亲。 传闻我的和亲对象是个残疾皇子,可洞房花烛夜当晚...
    茶点故事阅读 42,493评论 2 343

推荐阅读更多精彩内容