04 | 慎重使用正则表达式
04 | 慎重使用正则表达式
讲述:李良
时长13:01大小11.90M
什么是正则表达式?
正则表达式引擎
NFA 自动机的回溯
如何减少回溯问题?
正则表达式的优化
1. 少用贪婪模式,多用独占模式
2. 减少分支选择
3. 减少捕获嵌套
总结
思考题
赞 17
提建议
精选留言(69)
- Geek_99fab92019-05-28我没有你们优秀,我就明白以后少用点正则😄
编辑回复: 不一样的优秀~恭喜你学到了精华!
共 2 条评论74 - 胖2019-05-28字符串替换方法 Replace 普通字符替换 Replaceall 正则替换 一直觉得这两个方法的取名很具有迷惑性24
- 陆离2019-05-28老师{1,3}的意思不是最少匹配一次,最多匹配三次吗,独占模式那个例子为什么会不匹配呢?
作者回复: 你好,老师这里更正一下独占模式的例子,落了一个字符。ab{1,3}+bc
18 - K2019-06-01\\?(([A-Za-z0-9-~_=%]++\\&{0,1})+)。老师好,麻烦您讲解一下实际您当时是怎么优化的吗?从哪个正则改成了哪个正则,为什么能有这种优化。谢谢老师。
作者回复: 如果是单个+的情况下,是最大匹配规则,遇到特殊字符串时,会出现回溯问题。这里增加了一个+,变成两个++,变成了独占模式,避免回溯。
17 - 没有小名的曲儿2019-05-28老师,那个(X|Y|Z)三次index是什么意思呢
作者回复: 指的是String中的indexof方法
共 2 条评论17 - Liam2019-05-29文中提供的split性能消耗大的例子: \?(([A-Za-z0-9-~_=%]+)\&{0,1})$" 一个+ 表示量词,至少1个,不是独占模式吧,这里能否详细解释下优化点在哪里
作者回复: 你好,一个+表示匹配一个或多个,表示尽量多的匹配。我们这个再加一个+,\\?(([A-Za-z0-9-~_=%]++\\&{0,1})+)。提供的这个是没有优化的例子。
14 - ID1712019-06-11还是上边的例子,在字符后面加一个“+”,就可以开启独占模式。 text=“abbc” regex=“ab{1,3}+bc” 结果是不匹配,结束匹配,不会发生回溯问题。 这里的每一步做了什么,在最大匹配之后又发生了什么展开
作者回复: 1、匹配regex中的a和text中的a,匹配成功,继续匹配下一个字符; 2、匹配regex中的b{1,3}+,这个时候是最大匹配规则,也就是说text中会尽量多的去匹配b,直到满足3个b字符匹配成功,才会结束b{1,3}的匹配,这里可以直接匹配到text中的abb; 3、由于还没有满足最大3个的匹配需求,会继续匹配text中的c,发现不匹配,这个时候regex会跳到后面这个字符b,拿这个字符继续匹配; 4、regex中的b发现与text中的c不匹配,则进行回溯,回溯到text中的前一个字符b,发现匹配成功; 5、继续regex的下一个字符c与text中的c字符匹配,匹配成功,匹配结束。
共 12 条评论11 - ABC2019-05-30看完明白了回溯是什么意思,我总结如下: 回溯就比如,食堂吃饭,你一下拿了3个馒头。吃完两个,发现第三个不是你想吃的口味的时候,又把第三个放回去,这就造成了资源浪费。 避免的办法就是,一开始就只拿两个,觉得需要了再去继续拿,也就是懒惰模式。展开
作者回复: 理解很到位,懒惰就是有拿到馒头就走,非常懒,还有馒头拿也不要了。
9 - WL2019-05-28请问一下老师 "NFA 的状态数"这个概念感觉有点抽象我不太理解, 状态数是什么意思, 是NFA可以匹配的字符串的格式枚举吗?
作者回复: 你好 WL,就是不同的匹配格式,例如 ab{1,2}c,则状态数为2, 即 abc abbc。
8 - 135242656092019-09-09非捕获分组不用括号括起来不就好了么?
作者回复: 这个最直接了,效果是一样的
4 - 知易2020-08-11考古,文本"abbc" "ab{1,3}+c"匹配成功 "ab{1,3}+bc"匹配不成功时有疑惑,记录自己分析解惑流程。如有错误,欢迎指正,谢谢。 举个例子: String rgx1 = "ab{1,3}+c"; String rgx2 = "ab{1,3}+bc"; String rgx3 = "ab{1,3}+b{0,1}+c"; String text = "abbc"; 匹配流程(rgx1和text ab{1,3}+c 与 abbc): rgx1 text a a ab ab abb abb abbb abbc(为了找到最大匹配内容,回溯一次,rgx1吐出b{1,3}+中最后一个b) abbc abbc(吐出b{1,3}+中最后一个b后,用rgx1的c和文本的c来对比,对比成功) 匹配流程(rgx2和text ab{1,3}+bc 与 abbc): rgx2 text a a ab ab abb abb abbb abbc(为了找到最大匹配内容,回溯一次,rgx2吐出b{1,3}+中最后一个b) abbb abbc(吐出b{1,3}+中最后一个b后,用rgx2中紧跟着的b和文本的c来对比,不能回溯,对比失败) 总结,独占模式不能完全避免回溯,在通过限定字符(如b{1,3}+)会与文本比较获取最大匹配内容时回溯一次。 增加一个rgx3 和text对比 ab{1,3}+b{0,1}+c 与 abbc: rgx3 text a a ab ab abb abb abbb abbc(为了找到最大匹配内容,回溯一次,rgx3吐出b{1,3}+中最后一个b) abbb abbc(用rgx3中紧跟着的b{0,1}+和文本的c来对比,为了找到最大匹配内容,回溯一次,rgx3吐出b{0,1}+中第一个b) abbc abbc(吐出b{0,1}+中第一个b后,用rgx3中紧跟着的c和文本的c来对比,对比成功) 这里统计,为了找寻各自最大匹配内容,b{1,3}+时回溯一次,b{0,1}+时回溯一次。展开共 2 条评论4
- 郁陌陵2019-07-05老师,我理解独占模式可以减少回溯,但是不能避免回溯: String regex = "^ab{1,3}+c$"; String str = "abbc"; 这个例子里,b{1,3}+ 在匹配到 abb后,无法匹配c,是需要回溯的
作者回复: 是的,你理解的很到位,可以减少回溯,但是无法避免。这种第一次匹配会是匹配失败。具体的过程是text的a与regex的a匹配,然后继续text的b与b匹配,也匹配,这个时候由于是懒惰模式,要尽可能少的匹配,所以下一个text的b将与c匹配,匹配失败,这个时候又会回溯一次,将text的b与regex的b进行匹配,成功了,再将text中的c与c匹配,最后匹配成功。 这种方式与贪婪模式的匹配的方式是不一样的,虽然也发生了回溯,但回溯的方式不一样,是尽可能少的去匹配而发生的回溯。
共 4 条评论2 - ROAD2019-06-06replaceall2
- 一步2020-01-09对与懒惰模式,是不是也有回溯的?比如下面这个: /ab{1,3}?c/.test('abbc'); // true 当第一个 b 匹配成功后,取正则表达式的下一个字符 c 去匹配字符串,然后发现下一个字符还是b就会重新取正则表达式的上一个 b
作者回复: 是的,b与c匹配不成功之后,又会回溯到b与b匹配,成功之后,再匹配c与c,这个也是回溯。
1 - 稚者2019-09-29ab{1,3}+c 这个正则好诡异,既然都是独占了,也就是必须正好匹配3个,那量词 "1," 就没用了, 那就是 ab{3}+c ,可这样, 它跟 ab{3}c 岂不又是一样?
作者回复: ab{1,3}+c 跟 ab{3}c是一样的,但独占的使用场景有很多,例如在配合.*+可以最大极限的匹配,避免发生回溯的情况下实现匹配。
共 2 条评论1 - Vincent2019-09-09正则表达式还分贪婪模式,懒惰模式,独占模式,学习到了新技能,但是对于独占模式一旦匹配失败就返回不成功,是不是有落网之鱼?
作者回复: 是的,根据需求来定
1 - 钱2019-09-07课后思考及问题 任何一个细节问题,都有可能导致性能问题,而这背后折射出来的是我们对这项技术的了解不够透彻。所以我鼓励你学习性能调优,要掌握方法论,学会透过现象看本质。——严重认同,不过必须基础扎实才有机会。 没有完全懂,只知道使用正则表达式有坑,幸好我几乎不用正则表达式,以后也尽量不使用。展开1
- Vincent2019-07-21一开始不理解什么是正则回溯问题,原来是匹配到了不要的字符。
作者回复: 对的
1 - ddddd🐳2019-07-12贪婪总有存在的价值吧;贪恋相比于独占两者匹配结果是不同的,但是贪婪相比于懒惰模式呢,总有优势在吧?
作者回复: 对的,根据业务需要来定,贪婪模式会最大匹配字符。
1 - Geek_d93d562019-06-04我的测试代码 long l1 = System.currentTimeMillis(); for (int i = 0; i < 10000000; i++) { "abc".matches("ab{1,3}c"); } System.out.println("1111: " + (System.currentTimeMillis()-l1)); long l2 = System.currentTimeMillis(); for (int i = 0; i < 10000000; i++) { "abc".matches("ab{1,3}?c"); } System.out.println("2222: " + (System.currentTimeMillis()-l2)); 输出结果: 1111: 6348 2222: 6329 消耗时间差不多,没有什么差别,请问是正常的吗展开
作者回复: 因为你的这个例子发生回溯非常少,几乎可以忽略了,可以写个回溯比较长的。
1