您当前的位置:首页 > 电脑百科 > 程序开发 > 编程百科

如何掌握正则表达式这一开发利器,看这篇就够了

时间:2021-02-24 10:26:02  来源:  作者:

作者:mathe,腾讯QQ音乐前端开发工程师

正则表达式具有伟大技术发明的一切特点,它简单、优美、功能强大、妙用无穷。对于很多实际工作来讲,正则表达式简直是灵丹妙药,能够成百倍地提高开发效率和程序质量。

1. 正则常见规则

1.1 字符匹配

字符说明转义符d[0-9]。表示是一位数字。D[^0-9]。表示除数字外的任意字符。w[0-9a-zA-Z_]。表示数字、大小写字母和下划线。W[^0-9a-zA-Z_]。非单词字符。s[tvnrf]。表示空白符,包括空格、水平制表符、
垂直制表符、换行符、回车符、换页符。S[^tvnrf]。非空白符。.[^nr]。通配符,表示几乎任意字符。
换行符、回车符、行分隔符和段分隔符除外。uxxxx查找以十六进制数 xxxx 规定的 Unicode 字符。f匹配一个换页符 (U+000C)。n匹配一个换行符 (U+000A)。r匹配一个回车符 (U+000D)。t匹配一个水平制表符 (U+0009)。v匹配一个垂直制表符 (U+000B)。匹配 NULL(U+0000)字符, 不要在这后面跟其它小数,因为 是一个
八进制转义序列。[b]匹配一个退格(U+0008)。(不要和b 混淆了。)[abc]any of a, b, or c[^abc]not a, b, or c[a-g]character between a & g

1.2 位置匹配

字符说明b是单词边界,具体就是w 和W 之间的位置,也包括w 和 ^ 之间的位置,
也包括w 和之间的位置。具体说来就是与、与、与,与之间的位置。B是b 的反面的意思,非单词边界。例如在字符串中所有位置中,扣掉b,
剩下的都是B 的。^abc$字符串开始、结束的位置

1.3 组

字符说明(abc)capture group,捕获组nbackreference to group #n,分组引用,引用第 n 个捕获组匹配的内容,
其中 n 是正整数(?:abc)non-capturing group,非捕获组

1.4 先行断言

字符说明a(?=b)positive lookahead,先行断言,a 只有在 b 前面才匹配a(?!b)negative lookahead,先行否定断言,a 只有不在 b 前面才匹配

1.5 后行断言

字符说明(?<=b)apositive lookbehind,后行断言,a 只有在 b 后面才匹配(?<!b)anegative lookbehind,后行否定断言,a 只有不在 b 后面才匹配

1.6 量词和分支

字符说明a*0 or morea+1 or morea?0 or 1a{5}exactly fivea{2,}two or morea{1,3}between one & threea+?
a{2,}?match as few as possible,惰性匹配,就是尽可能少的匹配

以下都是惰性匹配:
{m,n}?
{m,}?
??
+?
*?

1.7 分支

字符说明ab|cdmatch ab or cd,匹配'ab'或者'cd'字符子串

1.8 修饰符

字符说明i执行对大小写不敏感的匹配。g执行全局匹配(查找所有匹配而非在找到第一个匹配后停止)。m执行多行匹配。u开启"Unicode 模式",用来正确处理大于uFFFF 的 Unicode 字符。也就是说,会正确处理四个字节的 UTF-16 编码。s允许 . 匹配换行符。yy 修饰符的作用与 g 修饰符类似,也是全局匹配,后一次匹配都从上一次匹配成功的下一个位置开始。不同之处在于,g 修饰符只要剩余位置中存在匹配就可,而 y 修饰符确保匹配必须从剩余的第一个位置开始,这也就是"粘连"的涵义

2. 运算符优先级

运算符描述转义符(), (?:), (?=), []圆括号和方括号*, +, ?, {n}, {n,}, {n,m}限定符^, $, 任何元字符、任何字符定位点和序列(即:位置和顺序)|替换,"或"操作
字符具有高于替换运算符的优先级,使得"m|food"匹配"m"或"food"。若要匹配"mood"或"food",请使用括号创建子表达式,从而产生"(m|f)ood"。

3. 正则回溯

3.1 什么是回溯算法

以下是来自摘自维基百科的部分解释:

回溯法是一种通用的计算机算法,用于查找某些计算问题的所有(或某些)解决方案,特别是约束满足问题,逐步构建候选解决方案,并在确定候选不可能时立即放弃候选("回溯")完成有效的解决方案。

回溯法通常用最简单的递归方法来实现,在反复重复上述的步骤后可能出现两种情况:

找到一个可能存在的正确的答案

在尝试了所有可能的分步方法后宣告该问题没有答案

在最坏的情况下,回溯法会导致一次复杂度为指数时间的计算。

3.2 什么是正则回溯

正则引擎主要可以分为两大类:一种是 DFA(Deterministic finite automaton 确定型有穷自动机),另一种是 NFA(NFA Non-deterministic finite automaton  非确定型有穷自动机)。NFA 速度较 DFA 更慢,并且实现复杂,但是它又有着比 DFA 强大的多的功能,比如支持反向引用等。像 JAVAScript、java、phpPython、c#等语言的正则引擎都是 NFA 型,NFA 正则引擎的实现过程中使用了回溯。

3.2.1 没有回溯的正则

举一个网上常见的例子,正则表达式/ab{1,3}c/g 去匹配文本'abbc',我们接下来会通过 RegexBuddy 分析其中的匹配过程,后续的一个章节有关于 RegexBuddy 的使用介绍。

如何掌握正则表达式这一开发利器,看这篇就够了

 

如上图所示,让我们一步一步分解匹配过程:

  1. 正则引擎先匹配 a。
  2. 正则引擎尽可能多地(贪婪)匹配 b。
  3. 正则引擎匹配 c,完成匹配。

在这之中,匹配过程都很顺利,并没发生意外(回溯)。

3.2.2 有正则回溯的正则

让我们把上面的正则修改一下,/ab{1,3}c/g 改成/ab{1,3}bc/g,接下再通过 RegexBuddy 查看分析结果。

如何掌握正则表达式这一开发利器,看这篇就够了

 

我们再一步一步分解匹配过程:

  1. 正则引擎先匹配 a。
  2. 正则引擎尽可能多地(贪婪)匹配 b{1,3}中的 b。
  3. 正则引擎去匹配 b,发现没 b 了,糟糕!赶紧回溯!
  4. 返回 b{1,3}这一步,不能这么贪婪,少匹配个 b。
  5. 正则引擎去匹配 b。
  6. 正则引擎去匹配 c,完成匹配。

以上,就是一个简单的回溯过程。

3.3 正则回溯的几种常见形式

从上面发生正则回溯的例子可以看出来,正则回溯的过程就是一个试错的过程,这也是回溯算法的精髓所在。回溯会增加匹配的步骤,势必会影响文本匹配的性能,所以,要想提升正则表达式的匹配性能,了解回溯出现的场景(形式)是非常关键的。

3.3.1 贪婪量词

在 NFA 正则引擎中,量词默认都是贪婪的。当正则表达式中使用了下表所示的量词,正则引擎一开始会尽可能贪婪的去匹配满足量词的文本。当遇到匹配不下去的情况,就会发生回溯,不断试错,直至失败或者成功。

量词说明a*0 or morea+1 or morea?0 or 1a{5}exactly fivea{2,}two or morea{1,3}between one & three

当多个贪婪量词挨着存在,并相互有冲突时,秉持的是"先到先得"的原则,如下所示:

let string = "12345";

let regex = /(d{1,3})(d{1,3})/;
console.log( string.match(regex) );
// => ["12345", "123", "45", index: 0, input: "12345"]

3.3.2 惰性量词

贪婪是导致回溯的重要原因,那我们尽量以懒惰匹配的方式去匹配文本,是否就能避免回溯了呢?答案是否定的。

让我们还是看回最初的例子,/ab{1,3}c/g 去匹配 abbc。接下来,我们再把正则修改一下,改成/ab{1,3}?c/g 去匹配 abbc,以懒惰匹配的方式去匹配文本,RegexBuddy 执行步骤如下图所示:

如何掌握正则表达式这一开发利器,看这篇就够了

 

  1. 正则引擎先匹配 a。
  2. 正则引擎尽可能少地(懒惰)匹配 b{1,3}中的 b。
  3. 正则引擎去匹配 c,糟糕!怎么有个 b 挡着,匹配不了 c 啊!赶紧回溯!
  4. 返回 b{1,3}这一步,不能这么懒惰,多匹配个 b。
  5. 正则引擎再去匹配 c,糟糕!怎么还有 b 挡着,匹配不了 c 啊!赶紧回溯!
  6. 返回 b{1,3}这一步,不能这么懒惰,再多匹配个 b。
  7. 正则引擎再去匹配 c,匹配成功,棒棒哒!

本来是好端端不会发生回溯的正则,因为使用了惰性量词进行懒惰匹配后,反而产生了回溯了。所以说,惰性量词也不能瞎用,关键还是要看场景。

3.3.3 分组

分支的匹配规则是:按照分支的顺序逐个匹配,当前面的分支满足要求了,则舍弃后面的分支。

举个简单的分支栗子,使用正则表达式去匹配 /abcde|abc/g 文本 abcd,还是通过 RegexBuddy 查看执行步骤:

如何掌握正则表达式这一开发利器,看这篇就够了

 

  1. 正则引擎匹配 a。
  2. 正则引擎匹配 b。
  3. 正则引擎匹配 c。
  4. 正则引擎匹配 d。
  5. 正则引擎匹配 e,糟糕!下一个并不是 e,赶紧回溯!
  6. 上一个分支走不通,切换分支,第二个分支正则引擎匹配 a。
  7. 第二个分支正则引擎匹配 b。
  8. 第二个分支正则引擎匹配 c,匹配成功!

由此,可以看出,分组匹配的过程,也是个试错的过程,中间是可能产生回溯的。

4. 正则的分析与调试

RegexBuddy 是个十分强大的正则表达式学习、分析及调试工具。RegexBuddy 支持 C++、Java、JavaScript、Python 等十几种主流编程语言。通过 RegexBuddy,能看到正则一步步创建的过程。结合测试文本,你能看到正则一步步执行匹配的过程,这对于理解正则回溯和对正则进行进一步优化,都有极大的帮助。

4.1 安装分析调试工具

可以在 RegexBuddy 的官方网站下载及获取 RegexBuddy。

下载完后,一步步点击安装即可。

如何掌握正则表达式这一开发利器,看这篇就够了

 

4.2 工具界面介绍

下图便是 RegexBuddy 界面的各个面板及相关功能。

如何掌握正则表达式这一开发利器,看这篇就够了

 

4.3 创建正则

为了方便使用,可以在布局设置那里将布局设置成 Side by Side Layout。

在正则输入区输入你的正则 regex1,查看 Create 面板,就会发现面板上显示了正则的创建过程(或者说是匹配规则),在 Test 面板区域输入你的测试文本,满足 regex1 匹配规则的部分会高亮显示,如下图所示。

如何掌握正则表达式这一开发利器,看这篇就够了

 

4.4 使用 RegexBuddy 的 Debug 功能

选中测试文本,点击 debug 就可以进入 RegexBuddy 的 debug 模式,个人觉得这是 RegexBuddy 最强大地方,因为它可以让你清楚地知道你输入的正则对测试文本的匹配过程,执行了多少步,哪里发生了回溯,哪里需要优化,你都能一目了然。

如何掌握正则表达式这一开发利器,看这篇就够了

 

4.5 使用 RegexBuddy 的 Library 功能

RegexBuddy 的正则库内置了很多常用正则,日常编码过程中需要的很多正则表达式都能在该正则库中找到。

如何掌握正则表达式这一开发利器,看这篇就够了

 

4.6 更多工具推荐

  • 正则可视化-regexper
  • 正则可视化-regulex
  • 正则在线调试

5. 正则性能优化

正则是个很好用的利器,如果使用得当,如有神助,能省掉大量代码。当如果使用不当,则是处处埋坑。所以,本章节的重点就是总结如何写一个高性能的正则表达式。

5.1 避免量词嵌套

举个简单的例子对比:

我们使用正则表达式/a*b/去匹配字符串 aaaaa,看下图 RegexBuddy 的执行过程:

如何掌握正则表达式这一开发利器,看这篇就够了

 

我们将以上正则修改成/(a*)*b/去匹配字符串 aaaaa,再看看 RegexBuddy 的执行结果过程:

如何掌握正则表达式这一开发利器,看这篇就够了

 

以上两个正则的基本执行步骤可以简单认为是:

  1. 贪婪匹配
  2. 回溯
  3. 直至发现匹配失败

但令人惊奇的是,第一个正则的从开始匹配到匹配失败这个过程只有 14 步。而第二个正则却有 128 步之多。可想而知,嵌套量词会大大增加正则的执行过程。因为这其中进行了两层回溯,这个执行步骤增加的过程就如同算法复杂度从 O(n)上升到 O(n^2)的过程一般。

所以,面对量词嵌套,我们需作出适当的转化消除这些嵌套:

(a*)* <=> (a+)* <=> (a*)+ <=> a*
(a+)+ <=> a+

5.2 使用非捕获组

NFA 正则引擎中的括号主要有两个作用:

  1. 主流功能,提升括号中内容的运算优先级
  2. 反向引用

反向引用这个功能很强大,强大的代价是消耗性能。所以,当我们如果不需要用到括号反向引用的功能时,我们应该尽量使用非捕获组,也就是:

// 捕获组与非捕获组
() => (?:)

5.3 分支优化

分支也是导致正则回溯的重要原因,所以,针对正则分支,我们也需要作出必要的优化。

5.3.1 减少分支数量

首先,需要减少分支数量。比如不少正则在匹配 http 和 https 的时候喜欢写成:

/^http|https/

其实上面完全可以优化成:

/^https?/

这样就能减少没必要的分支回溯

5.3.2 缩小分支内的内容

缩小分支中的内容也是很有必要的,例如我们需要匹配 this 和 that ,我们也许会写成:

/this|that/

但上面其实完全可以优化成

/th(?:is|at)/

有人可能认为以上没啥区别,实践出真知,让我们用以上两个正则表达式去匹配一下 that。

如何掌握正则表达式这一开发利器,看这篇就够了

 


如何掌握正则表达式这一开发利器,看这篇就够了

 

我们会发现第一个正则的执行步骤比第一个正则多两步,那是因为第一个正则的回溯路径比第二个正则的回溯路径更长了,最终导致执行步骤变长。

5.4 锚点优化

在能使用锚点的情况下尽量使用锚点。大部分正则引擎会在编译阶段做些额外分析, 判断是否存在成功匹配必须的字符或者字符串。类似^、$ 这类锚点匹配能给正则引擎更多的优化信息。

例如正则表达式 hello(hi)?$ 在匹配过程中只可能从字符串末尾倒数第 7 个字符开始, 所以正则引擎能够分析跳到那个位置, 略过目标字符串中许多可能的字符, 大大提升匹配速度。

6. 结语

曾经有一次因为写一个性能恶劣的正则表达式,导致代码执行过程因为性能问题挂掉。于是下定决心要把正则表达式搞明白,看了不少文章书籍,做了不少练习之后,总算摸到了些门道,也真真切切体会到正则表达式的优美和强大。写下此文,记录下一些学习心得和总结,望批评指正,共同进步。



Tags:正则表达式   点击:()  评论:()
声明:本站部分内容及图片来自互联网,转载是出于传递更多信息之目的,内容观点仅代表作者本人,如有任何标注错误或版权侵犯请与我们联系(Email:2595517585@qq.com),我们将及时更正、删除,谢谢。
▌相关推荐
一、简介很多时候我们都需要用到一些验证的方法,有时候需要用正则表达式校验数据时,往往需要到网上找很久,结果找到的还不是很符合自己想要的。所以我把自己整理的校验帮助类分...【详细内容】
2021-12-27  Tags: 正则表达式  点击:(1)  评论:(0)  加入收藏
1. 什么是正则表达式正则表达式(Regular Expressions),也称为 “regex” 或 “regexp” 是使用单个字符串来描述、匹配一系列匹配某个句法规则的字符串,这样程序就可以将该模...【详细内容】
2021-11-26  Tags: 正则表达式  点击:(25)  评论:(0)  加入收藏
写在前面我们在日常的Java开发中,经常需要处理一些字符串,这个时候正则表达式是非常有用的。几乎在所有的编程语言中都支持正则表达式。以下我将压箱底多年的干货搬出来给大家...【详细内容】
2021-10-25  Tags: 正则表达式  点击:(28)  评论:(0)  加入收藏
正则作为处理字符串的一个实用工具,在Python中经常会用到,比如爬虫爬取数据时常用正则来检索字符串等等。正则表达式已经内嵌在Python中,通过导入re模块就可以使用,作为刚学Pyth...【详细内容】
2021-08-31  Tags: 正则表达式  点击:(77)  评论:(0)  加入收藏
打算给闺女买几本书,计划是列一个书单让闺女自己选。于是找到另一个还算不错的书单介绍页,可惜它不让复制,鼠标右键选中提示扫码以后就跳到学而思的网页让加老师的微信之类的。...【详细内容】
2021-08-10  Tags: 正则表达式  点击:(83)  评论:(0)  加入收藏
正则表达式到底是个啥?在编写处理字符串的程序或网页时,经常会有查找符合某些复杂规则的字符串的需要。正则表达式就是用于描述这些规则的工具。换句话说,正则表达式就是记录...【详细内容】
2021-04-27  Tags: 正则表达式  点击:(243)  评论:(0)  加入收藏
作者:mathe,腾讯QQ音乐前端开发工程师正则表达式具有伟大技术发明的一切特点,它简单、优美、功能强大、妙用无穷。对于很多实际工作来讲,正则表达式简直是灵丹妙药,能够成百倍地...【详细内容】
2021-02-24  Tags: 正则表达式  点击:(198)  评论:(0)  加入收藏
概述正则表达式非常强大,能够解决我们开发时用于匹配一些特定的要求。并且java中还提供了相当强大的方法,扩展其功能性。下面我们就来介绍一下:1.定义一个关于用户名的正则表达...【详细内容】
2020-08-11  Tags: 正则表达式  点击:(68)  评论:(0)  加入收藏
“代码胜于雄辩。”&mdash;&mdash;林纳斯&middot;托瓦兹(Linus Torvalds)许多编程语言和操作系统都支持正则表达式(regular expression):定义搜索模式的一组字符串。正则表达式可...【详细内容】
2020-07-14  Tags: 正则表达式  点击:(37)  评论:(0)  加入收藏
正则表达式 - 语法正则表达式(regular expression)描述了一种字符串匹配的模式,可以用来检查一个串是否含有某种子串、将匹配的子串做替换或者从某个串中取出符合某个条件的...【详细内容】
2020-07-05  Tags: 正则表达式  点击:(53)  评论:(0)  加入收藏
▌简易百科推荐
摘 要 (OF作品展示)OF之前介绍了用python实现数据可视化、数据分析及一些小项目,但基本都是后端的知识。想要做一个好看的可视化大屏,我们还要学一些前端的知识(vue),网上有很多比...【详细内容】
2021-12-27  项目与数据管理    Tags:Vue   点击:(1)  评论:(0)  加入收藏
程序是如何被执行的&emsp;&emsp;程序是如何被执行的?许多开发者可能也没法回答这个问题,大多数人更注重的是如何编写程序,却不会太注意编写好的程序是如何被运行,这并不是一个好...【详细内容】
2021-12-23  IT学习日记    Tags:程序   点击:(9)  评论:(0)  加入收藏
阅读收获✔️1. 了解单点登录实现原理✔️2. 掌握快速使用xxl-sso接入单点登录功能一、早期的多系统登录解决方案 单系统登录解决方案的核心是cookie,cookie携带会话id在浏览器...【详细内容】
2021-12-23  程序yuan    Tags:单点登录(   点击:(8)  评论:(0)  加入收藏
下载Eclipse RCP IDE如果你电脑上还没有安装Eclipse,那么请到这里下载对应版本的软件进行安装。具体的安装步骤就不在这赘述了。创建第一个标准Eclipse RCP应用(总共分为六步)1...【详细内容】
2021-12-22  阿福ChrisYuan    Tags:RCP应用   点击:(7)  评论:(0)  加入收藏
今天想简单聊一聊 Token 的 Value Capture,就是币的价值问题。首先说明啊,这个话题包含的内容非常之光,Token 的经济学设计也可以包含诸多问题,所以几乎不可能把这个问题说的清...【详细内容】
2021-12-21  唐少华TSH    Tags:Token   点击:(9)  评论:(0)  加入收藏
实现效果:假如有10条数据,分组展示,默认在当前页面展示4个,点击换一批,从第5个开始继续展示,到最后一组,再重新返回到第一组 data() { return { qList: [], //处理后...【详细内容】
2021-12-17  Mason程    Tags:VUE   点击:(14)  评论:(0)  加入收藏
什么是性能调优?(what) 为什么需要性能调优?(why) 什么时候需要性能调优?(when) 什么地方需要性能调优?(where) 什么时候来进行性能调优?(who) 怎么样进行性能调优?(How) 硬件配...【详细内容】
2021-12-16  软件测试小p    Tags:性能调优   点击:(19)  评论:(0)  加入收藏
Tasker 是一款适用于 Android 设备的高级自动化应用,它可以通过脚本让重复性的操作自动运行,提高效率。 不知道从哪里听说的抖音 app 会导致 OLED 屏幕烧屏。于是就现学现卖,自...【详细内容】
2021-12-15  ITBang    Tags:抖音防烧屏   点击:(23)  评论:(0)  加入收藏
11 月 23 日,Rust Moderation Team(审核团队)在 GitHub 上发布了辞职公告,即刻生效。根据公告,审核团队集体辞职是为了抗议 Rust 核心团队(Core team)在执行社区行为准则和标准上...【详细内容】
2021-12-15  InfoQ    Tags:Rust   点击:(24)  评论:(0)  加入收藏
一个项目的大部分API,测试用例在参数和参数值等信息会有很多相似的地方。我们可以复制API,复制用例来快速生成,然后做细微调整既可以满足我们的测试需求1.复制API:在菜单发布单...【详细内容】
2021-12-14  AutoMeter    Tags:AutoMeter   点击:(20)  评论:(0)  加入收藏
最新更新
栏目热门
栏目头条