正则表达式(Regular Expression)是对字符串操作的一种逻辑公式,就是用事先定义好的一些特定字符、及这些特定字符的组合,组成一个“规则字符串”,这个“规则字符串”用来表达对字符串的一种过滤逻辑。

给定一个正则表达式和另一个字符串,我们可以达到如下的目的:

  1. 给定的字符串是否符合正则表达式的过滤逻辑(称作“匹配”);
  2. 可以通过正则表达式,从字符串中获取我们想要的特定部分。

历史: 维基百科 - 正则表达式

基础语法

正则表达式30分钟入门教程

菜鸟教程: 正则表达式 - 语法

优先权

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

正则性能优化

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

避免量词嵌套

举个简单的例子对比:

我们使用正则表达式 /a*b/ 去匹配字符串 aaaaa

我们将以上正则修改成 /(a*)*b/ 去匹配字符串 aaaaa

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

  1. 贪婪匹配

  2. 回溯

  3. 直至发现匹配失败

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

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

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

使用非捕获组

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

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

  2. 反向引用

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

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

分支优化

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

a. 减少分支数量

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

1
/^http|https/ 

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

1
/^https?/

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

b. 缩小分支内的内容

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

1
/this|that/

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

1
/th(?:is|at)/

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

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

锚点优化

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

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

RegexBuddy

RegexBuddy

性能优化完整版: 腾讯技术工程的知乎回答

匹配中文

\w 匹配的仅仅是中文,数字,字母,对于国人来讲,仅匹配中文时常会用到,见下

中文汉字:

1
匹配中文字符的正则表达式: [\u4e00-\u9fa5]

双字节(包含中日韩等):

1
匹配双字节字符(包括汉字在内):[^\x00-\xff]

相关资源

正则可视化 - regexper

在线测试 - regexr

性能调试工具 - RegexBuddy

最全的常用正则表达式大全