正则表达式具备伟大科技发明的所有特征,简单、美观、强大、用处无穷。对于很多实际任务来说,正则表达式简直就是一剂灵丹妙药,可以将开发效率和程序质量提高数百倍。1.正则化常用规则1.1字符匹配字符描述\转义字符\d[0-9]。代表一个数字。\D[^0-9]。表示除数字以外的任何字符。\w[0-9a-zA-Z_]。表示数字、大小写字母和下划线。\W[^0-9a-zA-Z_]。非单词字符。\s[\t\v\n\r\f]。表示空白字符,包括空格、水平制表符、垂直制表符、换行符、回车符和换页符。\S[^\t\v\n\r\f]。非空白。.[^\n\r]。通配符,几乎代表任何字符。换行符、回车符、行分隔符和段分隔符除外。\uxxxx查找由十六进制数xxxx指定的Unicode字符。\f匹配换页符(U+000C)。\n匹配换行符(U+000A)。\r匹配回车(U+000D)。\t匹配水平制表符(U+0009)。\v匹配垂直制表符(U+000B)。\0匹配NULL(U+0000)字符,后面不要跟其他小数,因为\0是八进制转义序列。[\b]匹配退格键(U+0008)。(不要与\b混淆。)[abc]a,b,orc[^abc]nota,b,orc[a-g]characterbetweena&g1.2位置匹配字符描述\b是一个词边界,具体是\w和\W之间的位置,也包括\w和^之间的位置,也包括\w和之间的位置。具体来说就是and、and、and、and之间的位置。\B是\b的反义词,不是单词边界。比如字符串中的所有位置,都减去\b,剩下的都是\B。^abc$字符串的起止位置1.3组字符说明(abc)capturegroup,capturegroup\nbackreferencetogroup#n,groupreference,引用第n个捕获组匹配的内容,其中n为正整数(?:abc)non-capturinggroup,non-capturinggroup1.4Preambleassertioncharacterdescriptiona(?=b)positivelookahead,preemptiveassertion,aonlymatchesbeforeb1.5Backlineassertioncharacterdescription(?<=b)positivelookbehind,backlineassertion,aonlymatchesafterb(?["12345","123","45",index:0,input:"12345"]3.3.2惰性量词的贪心是回溯的重要原因,那我们Try惰性匹配文本,能不能避免回溯?答案是否定的。让我们回到最初的例子,/ab{1,3}c/g来匹配abbc。接下来,我们将regex修改为/ab{1,3}?c/g来匹配abbc,偷懒匹配文本。RegexBuddy执行步骤如下图所示:正则引擎匹配第一个。正则表达式引擎尽可能少地(懒惰地)匹配binb{1,3}。正则引擎去匹配c,哎呀!为什么有b挡住了,所以匹配不到c!现在回溯!在返回b{1,3}这一步,不能偷懒多匹配b。正则引擎又匹配c了,哎呀!怎么还有b挡着,所以匹配不到c!现在回溯!在返回b{1,3}这一步,不能偷懒多匹配b。然后正则引擎匹配c,匹配成功,太棒了!本来不发生回溯是个好规则,因为使用惰性量词进行惰性匹配后,反而发生了回溯。所以惰性量词不能乱用,关键还是要看场景。3.3.3分组分支的匹配规则是:按照分支的先后顺序,一条一条匹配,当前面的分支满足要求时,舍弃后面的分支。举个简单的分支栗子,用正则表达式匹配/abcde|abc/g文本abcd,或者通过RegexBuddy查看执行步骤:正则引擎匹配a。正则表达式引擎匹配b。常规引擎匹配c。正则表达式引擎匹配d。正则表达式匹配e,哎呀!下一个不是e,赶紧原路返回!前一个分支不行,切换分支,第二个分支正则引擎匹配a。第二个分支正则表达式匹配b。第二个分支正则引擎匹配c,匹配成功!由此可见,组匹配的过程也是一个试错的过程,中间可能会出现回溯。4、正则表达式分析和调试RegexBuddy是一个非常强大的正则表达式学习、分析和调试工具。RegexBuddy支持C++、Java、JavaScript、Python等十几种主流编程语言。通过RegexBuddy,你可以看到一步步创建正则表达式的过程。结合测试文本,可以看到正则表达式一步一步的匹配过程,对于理解正则表达式的回溯,进一步优化正则表达式很有帮助。4.1安装分析调试工具RegexBuddy可在RegexBuddy官网下载获取。下载后点击一步步安装。4.2工具界面介绍下图为RegexBuddy界面的各个面板及相关功能。4.3创建正则表达式为了方便使用,可以在布局设置中将布局设置为SidebySideLayout。在正则输入区输入你的正则regex1,勾选创建面板,你会发现面板上显示了正则创建过程(或匹配规则),在测试面板区输入你的测试文本,符合匹配的部分regex1的规则会被高亮显示,如下图。4.4使用RegexBuddy的Debug功能选择测试文本,点击debug进入RegexBuddy的调试模式。个人认为这是RegexBuddy最强大的部分,因为它可以让你清楚地知道你输入的正则表达式与测试文本的匹配过程。Execute走了多少步,哪里回溯了,哪里需要优化,一目了然。4.5使用RegexBuddy的库功能RegexBuddy的正则库内置了很多常用的正则表达式,日常编码过程中需要用到的很多正则表达式都可以在这个正则库中找到。4.6更多推荐工具Regex可视化-regexperRegulus可视化-regulexRegulex在线调试五、Regex性能优化Regex是一个非常好用的工具。如果使用得当,可以节省不少代码,神助攻。使用不当,处处埋坑。因此,本章的重点是总结如何编写一个高性能的正则表达式。5.1避免量词嵌套举一个简单的例子进行对比:我们使用正则表达式/a*b/来匹配字符串aaaaa,如下图RegexBuddy的执行过程:我们将上面的正则表达式修改为/(a*)*b/匹配字符串aaaaa,看RegexBuddy的执行结果过程:上面两个正则的基本执行步骤可以简单的认为:整个过程只有14步,就匹配失败了。第二个正则表达式有多达128个步骤。可以想象,嵌套量词会大大增加正则化的执行流程。因为有两层回溯,增加执行步数的过程就像是算法复杂度从O(n)增加到O(n^2)的过程。因此,面对量词嵌套,我们需要进行适当的变换,消除这些嵌套:(a*)*<=>(a+)*<=>(a*)+<=>a*(a+)+<=>a+5.2使用非捕获组NFA正则引擎中的括号有两个主要作用:主流功能,提高括号内内容的操作优先级BackreferenceBackreference是一个非常强大的功能,但是强大的代价是表现。因此,如果我们不需要使用括号反向引用的功能,我们应该尽量使用非捕获组,即://捕获组和非捕获组()=>(?:)5.3分支优化分支也是导致定时回溯的重要原因,所以我们也需要对定时分支进行必要的优化。5.3.1减少分支数量首先,需要减少分支数量。比如在匹配http和https的时候,很多正则表达式喜欢这样写:/^http|https/其实上面可以优化成:/^https?/这样可以减少不必要的分支回溯5.3.2缩小内容在分支中分支中的内容也是非常必要的,比如我们需要匹配这个和那个,我们可以这样写:/this|that/但是上面其实可以优化成/th(?:is|at)/可能有人会觉得上面不是有什么区别,实践出真知,让我们用上面的两个正则表达式来匹配一下吧。我们会发现第一个正则的执行步数比第一个正则多了两步,这是因为第一个正则的回溯路径比第二个正则的回溯路径长,最终导致执行的步数更长。5.4锚点优化尽可能使用锚点。大多数正则引擎会在编译阶段做一些额外的分析,以确定是否存在成功匹配所必需的字符或字符串。像^和$这样的锚匹配可以给正则引擎更多的优化信息。例如,正则表达式hello(hi)?$在匹配过程中只能从字符串末尾的第7个字符开始,因此正则引擎可以分析并跳转到该位置,跳过目标字符串中许多可能的字符,大大提高了匹配速度。6.结语有一次因为写了一个性能很差的正则表达式,导致代码执行过程因为性能问题挂掉了。所以我下定决心要了解正则表达式。在阅读了大量的文章和书籍并做了很多练习之后,我终于得到了一些提示,真正体会到了正则表达式的美妙和强大。写下这篇文章,记录一些学习心得和总结,望批评指正,共同进步。7.参考正则表达式中的悲观回溯注意不要落入正则回溯的陷阱正则匹配原理分析learncodethehardway正则表达式系列总结wikipedia回溯精通正则表达式
