这个正则表达式不应该发生灾难性的回溯

有人可以解释为什么Java的正则表达式引擎会在这个正则表达式上进入灾难性的回溯模式吗? 根据我的判断,每次交替都是互相排斥的。

^(?:[^'\"\\s~:/@#\\|\\^\\&\\[\\]\\(\\)\\\\\\{\\}][^\"\\s~:/@#\\|\\^\\&\\[\\]\\(\\)\\\\\\{\\}]*| \"(?:[^\"]+|\"\")+\"| '(?:[^']+|'')+') 

文字: 'pão de açúcar itaucard mastercard platinum SUSTENTABILIDADE])

为一些替换添加所有格匹配修复了问题,但我不知道为什么 – Java的正则表达式lib必须非常错误才能在互斥的分支上回溯。

  ^(?:[^'\"\\s~:/@#\\|\\^\\&\\[\\]\\(\\)\\\\\\{\\}][^\"\\s~:/@#\\|\\^\\&\\[\\]\\(\\)\\\\\\{\\}]*| \"(?:[^\"]++|\"\")++\"| '(?:[^']++|'')++') 

编辑:最后添加Java版本 – 尽管本质上是笨拙,不可读和不可维护。


¡没有更丑陋的模式!

你需要做的第一件事就是以一种能够承受人类可读性和可维护性的任何可能希望的方式编写你的正则表达式。 您需要做的第二件事是对此进行分析,以了解它实际上在做什么。

这意味着至少你需要在Pattern.COMMENTS模式(或前缀"(?x)" )中编译它,然后添加空格以提供一些视觉肘部空间。 尽可能接近,你实际上想要匹配的模式是这样的:

 ^ (?: [^'"\s~:/@\#|^&\[\]()\\{}] # alt 1: unquoted [^"\s~:/@\#|^&\[\]()\\{}] * | " (?: [^"]+ | "" )+ " # alt 2: double quoted | ' (?: [^']+ | '' )+ ' # alt 3: single quoted ) 

如你所见,我已经在可能的地方引入了垂直和水平空白,以引导眼睛和思维作为一种认知分块。 我还删除了所有无关的反斜杠。 这些都是彻头彻尾的错误或混淆器,除了使读者感到困惑之外什么都不做。

请注意,在应用垂直空白时,我已经在同一列中生成了从一行到下一行的相同部分,以便您可以立即看到哪些部分相同以及哪些部分不同。

完成后,我终于可以看到你在这里的一个匹配锚定到开始,然后选择三个替代。 因此,我已经用描述性评论标记了这三个备选方案,因此无需猜测。

我还注意到你的第一个选择有两个微妙不同(否定)的方括号字符类。 第二个是缺少第一个中的单引号排除。 这是故意的吗? 即使是这样,我发现这对我的口味来说太过重复了; 部分或全部应该在变量中,这样您就不会冒更新一致性问题的风险。


剖析

您要做的两件事中的第二件也是更重要的是对此进行分析。 您需要确切地查看正在编译该模式的正则表达式程序,并且需要在运行数据时跟踪其执行情况。

Java的Pattern类目前无法做到这一点,虽然我已经与OraSun的当前代码保管人谈了很长时间,并且他都热衷于将这个function添加到Java并认为他确切知道如何做到这一点。 他甚至给我发了一个原型,它完成了第一部分:汇编。 所以我希望有一天可以使用它。

与此同时,让我们转而使用一种工具,其中正则表达式是编程语言本身的一个组成部分,而不是作为一种尴尬的事后想法。 虽然有几种语言符合这一标准,但在模式匹配方面,没有一种语言符合Perl所见的复杂程度。

这是一个等效的程序。

 #!/usr/bin/env perl use v5.10; # first release with possessive matches use utf8; # we have utf8 literals use strict; # require variable declarations, etc use warnings; # check for boneheadedness my $match = qr{ ^ (?: [^'"\s~:/@\#|^&\[\]()\\{}] [^"\s~:/@\#|^&\[\]()\\{}] * | " (?: [^"]+ | "" )+ " | ' (?: [^']+ | '' )+ ' ) }x; my $text = "'pão de açúcar itaucard mastercard platinum SUSTENTABILIDAD])"; my $count = 0; while ($text =~ /$match/g) { print "Got it: $&\n"; $count++; } if ($count == 0) { print "Match failed.\n"; } 

如果我们运行该程序,我们会得到匹配失败的预期答案。 问题是为什么以及如何。

我们现在想看看两件事:我们想看看模式编译的正则表达式程序,然后我们想跟踪该正则表达式程序的执行。

这两个都是由控制

 use re "debug"; 

pragma,也可以通过-Mre=debug在命令行中指定。 这就是我们在这里要做的,以避免黑客攻击源代码。

正则表达式编译

re调试pragma通常会显示模式的编译及其执行。 为了分离这些,我们可以使用Perl的“仅编译”开关-c ,它不会尝试执行它编译的程序。 这样我们所看到的就是编译模式。 这些产生以下36行输出:

 $ perl -c -Mre=debug /tmp/bt Compiling REx "%n ^ (?: [^'%"\s~:/@\#|^&\[\]()\\{}]%n [^%"\s~:/"... Final program: 1: BOL (2) 2: BRANCH (26) 3: ANYOF[^\x09\x0a\x0c\x0d "#&-)/:@[-\^{-~][^{unicode}+utf8::IsSpacePerl] (14) 14: STAR (79) 15: ANYOF[^\x09\x0a\x0c\x0d "#&()/:@[-\^{-~][^{unicode}+utf8::IsSpacePerl] (0) 26: BRANCH (FAIL) 27: TRIE-EXACT["'] (79) <"> (29) 29: CURLYX[0] {1,32767} (49) 31: BRANCH (44) 32: PLUS (48) 33: ANYOF[\x00-!#-\xff][{unicode_all}] (0) 44: BRANCH (FAIL) 45: EXACT <""> (48) 47: TAIL (48) 48: WHILEM[1/2] (0) 49: NOTHING (50) 50: EXACT <"> (79) <'> (55) 55: CURLYX[0] {1,32767} (75) 57: BRANCH (70) 58: PLUS (74) 59: ANYOF[\x00-&(-\xff][{unicode_all}] (0) 70: BRANCH (FAIL) 71: EXACT <''> (74) 73: TAIL (74) 74: WHILEM[2/2] (0) 75: NOTHING (76) 76: EXACT <'> (79) 78: TAIL (79) 79: END (0) anchored(BOL) minlen 1 /tmp/bt syntax OK Freeing REx: "%n ^ (?: [^'%"\s~:/@\#|^&\[\]()\\{}]%n [^%"\s~:/"... 

如您所见,编译的正则表达式程序本身就是一种“正则表达式汇编语言”。 (它看起来非常类似于向我展示的Java原型吐出的内容,所以我想有一天你也会在Java中看到这种东西。)所有细节都不是必需的,但我要指出,节点2的指令是BRANCH,如果失败,则继续执行分支26,另一个BRANCH。 第二个BRANCH是正则表达式程序的唯一其他部分,由一个TRIE-EXACT节点组成,因为它知道备选方案具有不同的起始字符串。 在我们将要讨论的那两个特里分支中会发生什么。

正则表达式执行

现在是时候看看它运行时会发生什么。 您正在使用的文本字符串会导致相当多的回溯,这意味着在最终失败之前,您将需要大量输出。 多少输出? 好吧,这么多:

 $ perl -Mre=debug /tmp/bt 2>&1 | wc -l 9987 

我认为10,000步是你所说的“灾难性的回溯模式”。 让我们看看我们不能把它简化为更易于理解的东西。 您的输入字符串长度为61个字符。 为了更好地了解正在发生的事情,我们可以将其修剪为'pão ,它只有4个字符。 (好吧,在NFC中,就是这样;它是NFD中的五个代码点,但这里没有任何改变)。 这导致167行输出:

 $ perl -Mre=debug /tmp/bt 2>&1 | wc -l 167 

实际上,这里是正则表达式(编译加)执行概要分析的行,当你的字符串长这么多字符时你会得到:

 chars lines string 1 63 ‹'› 2 78 ‹'p› 3 109 ‹'pã› 4 167 ‹'pão› 5 290 ‹'pão › 6 389 ‹'pão d› 7 487 ‹'pão de› 8 546 ‹'pão de › 9 615 ‹'pão de a› 10 722 ‹'pão de aç› .... 61 9987 ‹'pão de açúcar itaucard mastercard platinum SUSTENTABILIDAD])› 

当字符串是四个字符'pão时,让我们看一下调试输出。 这次我省略了编译部分只显示执行部分:

 $ perl -Mre=debug /tmp/bt Matching REx "%n ^ (?: [^'%"\s~:/@\#|^&\[\]()\\{}]%n [^%"\s~:/"... against "'p%x{e3}o" UTF-8 string... 0 <> <'p%x{e3}o> | 1:BOL(2) 0 <> <'p%x{e3}o> | 2:BRANCH(26) 0 <> <'p%x{e3}o> | 3: ANYOF[^\x09\x0a\x0c\x0d "#&-)/:@[-\^{-~][^{unicode}+utf8::IsSpacePerl](14) failed... 0 <> <'p%x{e3}o> | 26:BRANCH(78) 0 <> <'p%x{e3}o> | 27: TRIE-EXACT["'](79) 0 <> <'p%x{e3}o> | State: 1 Accepted: N Charid: 2 CP: 27 After State: 3 1 <'>  | State: 3 Accepted: Y Charid: 0 CP: 0 After State: 0 got 1 possible matches TRIE matched word #2, continuing only one match left, short-circuiting: #2 <'> 1 <'>  | 55: CURLYX[0] {1,32767}(75) 1 <'>  | 74: WHILEM[2/2](0) whilem: matched 0 out of 1..32767 1 <'>  | 57: BRANCH(70) 1 <'>  | 58: PLUS(74) ANYOF[\x00-&(-\xff][{unicode_all}] can match 3 times out of 2147483647... 5 <'p%x{e3}o> <> | 74: WHILEM[2/2](0) whilem: matched 1 out of 1..32767 5 <'p%x{e3}o> <> | 57: BRANCH(70) 5 <'p%x{e3}o> <> | 58: PLUS(74) ANYOF[\x00-&(-\xff][{unicode_all}] can match 0 times out of 2147483647... failed... 5 <'p%x{e3}o> <> | 70: BRANCH(73) 5 <'p%x{e3}o> <> | 71: EXACT <''>(74) failed... BRANCH failed... whilem: failed, trying continuation... 5 <'p%x{e3}o> <> | 75: NOTHING(76) 5 <'p%x{e3}o> <> | 76: EXACT <'>(79) failed... failed... 4 <'p%x{e3}>  | 74: WHILEM[2/2](0) whilem: matched 1 out of 1..32767 4 <'p%x{e3}>  | 57: BRANCH(70) 4 <'p%x{e3}>  | 58: PLUS(74) ANYOF[\x00-&(-\xff][{unicode_all}] can match 1 times out of 2147483647... 5 <'p%x{e3}o> <> | 74: WHILEM[2/2](0) whilem: matched 2 out of 1..32767 5 <'p%x{e3}o> <> | 57: BRANCH(70) 5 <'p%x{e3}o> <> | 58: PLUS(74) ANYOF[\x00-&(-\xff][{unicode_all}] can match 0 times out of 2147483647... failed... 5 <'p%x{e3}o> <> | 70: BRANCH(73) 5 <'p%x{e3}o> <> | 71: EXACT <''>(74) failed... BRANCH failed... whilem: failed, trying continuation... 5 <'p%x{e3}o> <> | 75: NOTHING(76) 5 <'p%x{e3}o> <> | 76: EXACT <'>(79) failed... failed... failed... 4 <'p%x{e3}>  | 70: BRANCH(73) 4 <'p%x{e3}>  | 71: EXACT <''>(74) failed... BRANCH failed... whilem: failed, trying continuation... 4 <'p%x{e3}>  | 75: NOTHING(76) 4 <'p%x{e3}>  | 76: EXACT <'>(79) failed... failed... 2 <'p> <%x{e3}o> | 74: WHILEM[2/2](0) whilem: matched 1 out of 1..32767 2 <'p> <%x{e3}o> | 57: BRANCH(70) 2 <'p> <%x{e3}o> | 58: PLUS(74) ANYOF[\x00-&(-\xff][{unicode_all}] can match 2 times out of 2147483647... 5 <'p%x{e3}o> <> | 74: WHILEM[2/2](0) whilem: matched 2 out of 1..32767 5 <'p%x{e3}o> <> | 57: BRANCH(70) 5 <'p%x{e3}o> <> | 58: PLUS(74) ANYOF[\x00-&(-\xff][{unicode_all}] can match 0 times out of 2147483647... failed... 5 <'p%x{e3}o> <> | 70: BRANCH(73) 5 <'p%x{e3}o> <> | 71: EXACT <''>(74) failed... BRANCH failed... whilem: failed, trying continuation... 5 <'p%x{e3}o> <> | 75: NOTHING(76) 5 <'p%x{e3}o> <> | 76: EXACT <'>(79) failed... failed... 4 <'p%x{e3}>  | 74: WHILEM[2/2](0) whilem: matched 2 out of 1..32767 4 <'p%x{e3}>  | 57: BRANCH(70) 4 <'p%x{e3}>  | 58: PLUS(74) ANYOF[\x00-&(-\xff][{unicode_all}] can match 1 times out of 2147483647... 5 <'p%x{e3}o> <> | 74: WHILEM[2/2](0) whilem: matched 3 out of 1..32767 5 <'p%x{e3}o> <> | 57: BRANCH(70) 5 <'p%x{e3}o> <> | 58: PLUS(74) ANYOF[\x00-&(-\xff][{unicode_all}] can match 0 times out of 2147483647. .. failed... 5 <'p%x{e3}o> <> | 70: BRANCH(73) 5 <'p%x{e3}o> <> | 71: EXACT <''>(74) failed... BRANCH failed... whilem: failed, trying continuation... 5 <'p%x{e3}o> <> | 75: NOTHING(76) 5 <'p%x{e3}o> <> | 76: EXACT <'>(79) failed... failed... failed... 4 <'p%x{e3}>  | 70: BRANCH(73) 4 <'p%x{e3}>  | 71: EXACT <''>(74) failed... BRANCH failed... whilem: failed, trying continuation... 4 <'p%x{e3}>  | 75: NOTHING(76) 4 <'p%x{e3}>  | 76: EXACT <'>(79) failed... failed... failed... 2 <'p> <%x{e3}o> | 70: BRANCH(73) 2 <'p> <%x{e3}o> | 71: EXACT <''>(74) failed... BRANCH failed... whilem: failed, trying continuation... 2 <'p> <%x{e3}o> | 75: NOTHING(76) 2 <'p> <%x{e3}o> | 76: EXACT <'>(79) failed... failed... failed... 1 <'>  | 70: BRANCH(73) 1 <'>  | 71: EXACT <''>(74) failed... BRANCH failed... failed... failed... BRANCH failed... Match failed Match failed. Freeing REx: "%n ^ (?: [^'%"\s~:/@\#|^&\[\]()\\{}]%n [^%"\s~:/"... 

您看到的情况是,trie快速分支到节点55,这是与单引号匹配的三个备选项中的最后一个,因为您的目标字符串以单引号开头。 就是这个:

  | ' (?: [^']+ | '' )+ ' # alt 3: single quoted 

节点55是这个特里分支:

  <'> (55) 55: CURLYX[0] {1,32767} (75) 57: BRANCH (70) 58: PLUS (74) 59: ANYOF[\x00-&(-\xff][{unicode_all}] (0) 70: BRANCH (FAIL) 71: EXACT <''> (74) 

这里是执行跟踪,显示您的灾难性退避发生的位置:

  1 <'>  | 74: WHILEM[2/2](0) whilem: matched 0 out of 1..32767 1 <'>  | 57: BRANCH(70) 1 <'>  | 58: PLUS(74) ANYOF[\x00-&(-\xff][{unicode_all}] can match 3 times out of 2147483647... 5 <'p%x{e3}o> <> | 74: WHILEM[2/2](0) whilem: matched 1 out of 1..32767 5 <'p%x{e3}o> <> | 57: BRANCH(70) 5 <'p%x{e3}o> <> | 58: PLUS(74) ANYOF[\x00-&(-\xff][{unicode_all}] can match 0 times out of 2147483647... failed... 5 <'p%x{e3}o> <> | 70: BRANCH(73) 5 <'p%x{e3}o> <> | 71: EXACT <''>(74) failed... BRANCH failed... whilem: failed, trying continuation... 5 <'p%x{e3}o> <> | 75: NOTHING(76) 5 <'p%x{e3}o> <> | 76: EXACT <'>(79) failed... failed... 4 <'p%x{e3}>  | 74: WHILEM[2/2](0) whilem: matched 1 out of 1..32767 4 <'p%x{e3}>  | 57: BRANCH(70) 4 <'p%x{e3}>  | 58: PLUS(74) ANYOF[\x00-&(-\xff][{unicode_all}] can match 1 times out of 2147483647... 5 <'p%x{e3}o> <> | 74: WHILEM[2/2](0) whilem: matched 2 out of 1..32767 

节点58吞噬了字符串中的所有3个剩余字符, pão 。 这导致单引号的终止完全匹配失败。 因此,它会尝试您的替代方案,即一对单引号,但也会失败。

在这一点上,我必须质疑你的模式。 不能

 ' (?: [^']+ | '' )+ ' 

真的很简单吗?

 ' [^']* ' 

所以正在发生的事情是它有很多方法可以回溯寻找逻辑上永远不会发生的东西。 你有一个嵌套的量词,这导致各种无望和无意识的繁忙工作。

如果我们交换简化模式到这个:

 ^ (?: [^'"\s~:/@\#|^&\[\]()\\{}] + | " [^"]* " | ' [^']* ' ) 

无论输入字符串的大小如何,它现在都提供相同数量的跟踪输出行:只有40,包括编译。 见证完整字符串的编译和执行:

 Compiling REx "%n ^ (?: [^'%"\s~:/@\#|^&\[\]()\\{}]%n [^%"\s~:/"... Final program: 1: BOL (2) 2: BRANCH (26) 3: ANYOF[^\x09\x0a\x0c\x0d "#&-)/:@[-\^{-~][^{unicode}+utf8::IsSpacePerl] (14) 14: STAR (61) 15: ANYOF[^\x09\x0a\x0c\x0d "#&()/:@[-\^{-~][^{unicode}+utf8::IsSpacePerl] (0) 26: BRANCH (FAIL) 27: TRIE-EXACT["'] (61) <"> (29) 29: STAR (41) 30: ANYOF[\x00-!#-\xff][{unicode_all}] (0) 41: EXACT <"> (61) <'> (46) 46: STAR (58) 47: ANYOF[\x00-&(-\xff][{unicode_all}] (0) 58: EXACT <'> (61) 60: TAIL (61) 61: END (0) anchored(BOL) minlen 1 Matching REx "%n ^ (?: [^'%"\s~:/@\#|^&\[\]()\\{}]%n [^%"\s~:/"... against "'p%x{e3}o de a%x{e7}%x{fa}car itaucard mast ercard platinum S"... UTF-8 string... 0 <> <'p%x{e3}o > | 1:BOL(2) 0 <> <'p%x{e3}o > | 2:BRANCH(26) 0 <> <'p%x{e3}o > | 3: ANYOF[^\x09\x0a\x0c\x0d "#&-)/:@[-\^{-~][^{unicode}+utf8::IsSpacePerl](14) failed... 0 <> <'p%x{e3}o > | 26:BRANCH(60) 0 <> <'p%x{e3}o > | 27: TRIE-EXACT["'](61) 0 <> <'p%x{e3}o > | State: 1 Accepted: N Charid: 2 CP: 27 After State: 3 1 <'>  | State: 3 Accepted: Y Charid: 0 CP: 0 After State: 0 got 1 possible matches TRIE matched word #2, continuing only one match left, short-circuiting: #2 <'> 1 <'>  | 46: STAR(58) ANYOF[\x00-&(-\xff][{unicode_all}] can match 60 times out of 2147483647... failed... BRANCH failed... Match failed Match failed. Freeing REx: "%n ^ (?: [^'%"\s~:/@\#|^&\[\]()\\{}]%n [^%"\s~:/"... 

我知道你认为占有匹配可能就是这里的答案,但我认为真正的问题是原始模式中的错误逻辑。 看看现在这种运作有多精益?

如果我们在旧模式上使用你的所有权运行它,即使我认为没有意义,我们仍然会获得恒定的运行时间,但它需要更多的步骤。 有这种模式

  ^ (?: [^'"\s~:/@\#|^&\[\]()\\{}] + # alt 1: unquoted | " (?: [^"]++ | "" )++ " # alt 2: double quoted | ' (?: [^']++ | '' )++ ' # alt 3: single quoted ) 

编译加执行配置文件如下:

 Compiling REx "%n ^ (?: [^'%"\s~:/@\#|^&\[\]()\\{}]%n [^%"\s~:/"... Final program: 1: BOL (2) 2: BRANCH (26) 3: ANYOF[^\x09\x0a\x0c\x0d "#&-)/:@[-\^{-~][^{unicode}+utf8::IsSpacePerl] (14) 14: STAR (95) 15: ANYOF[^\x09\x0a\x0c\x0d "#&()/:@[-\^{-~][^{unicode}+utf8::IsSpacePerl] (0) 26: BRANCH (FAIL) 27: TRIE-EXACT["'] (95) <"> (29) 29: SUSPEND (58) 31: CURLYX[0] {1,32767} (55) 33: BRANCH (50) 34: SUSPEND (54) 36: PLUS (48) 37: ANYOF[\x00-!#-\xff][{unicode_all}] (0) 48: SUCCEED (0) 49: TAIL (53) 50: BRANCH (FAIL) 51: EXACT <""> (54) 53: TAIL (54) 54: WHILEM[1/2] (0) 55: NOTHING (56) 56: SUCCEED (0) 57: TAIL (58) 58: EXACT <"> (95) <'> (63) 63: SUSPEND (92) 65: CURLYX[0] {1,32767} (89) 67: BRANCH (84) 68: SUSPEND (88) 70: PLUS (82) 71: ANYOF[\x00-&(-\xff][{unicode_all}] (0) 82: SUCCEED (0) 83: TAIL (87) 84: BRANCH (FAIL) 85: EXACT <''> (88) 87: TAIL (88) 88: WHILEM[2/2] (0) 89: NOTHING (90) 90: SUCCEED (0) 91: TAIL (92) 92: EXACT <'> (95) 94: TAIL (95) 95: END (0) anchored(BOL) minlen 1 Matching REx "%n ^ (?: [^'%"\s~:/@\#|^&\[\]()\\{}]%n [^%"\s~:/"... against "'p%x{e3}o de a%x{e7}%x{fa}car itaucard mastercard platinum S"... UTF-8 string... 0 <> <'p%x{e3}o > | 1:BOL(2) 0 <> <'p%x{e3}o > | 2:BRANCH(26) 0 <> <'p%x{e3}o > | 3: ANYOF[^\x09\x0a\x0c\x0d "#&-)/:@[-\^{-~][^{unicode}+utf8::IsSpacePerl](14) failed... 0 <> <'p%x{e3}o > | 26:BRANCH(94) 0 <> <'p%x{e3}o > | 27: TRIE-EXACT["'](95) 0 <> <'p%x{e3}o > | State: 1 Accepted: N Charid: 2 CP: 27 After State: 3 1 <'> | State: 3 Accepted: Y Charid: 0 CP: 0 After State: 0 got 1 possible matches TRIE matched word #2, continuing only one match left, short-circuiting: #2 <'> 1 <'> | 63: SUSPEND(92) 1 <'> | 65: CURLYX[0] {1,32767}(89) 1 <'> | 88: WHILEM[2/2](0) whilem: matched 0 out of 1..32767 1 <'> | 67: BRANCH(84) 1 <'> | 68: SUSPEND(88) 1 <'> | 70: PLUS(82) ANYOF[\x00-&(-\xff][{unicode_all}] can match 60 times out of 2147483647... 64  <| 82: SUCCEED(0) subpattern success... 64  <| 88: WHILEM[2/2](0) whilem: matched 1 out of 1..32767 64  <| 67: BRANCH(84) 64  <| 68: SUSPEND(88) 64  <| 70: PLUS(82) ANYOF[\x00-&(-\xff][{unicode_all}] can match 0 times out of 2147483647... failed... failed... 64  <| 84: BRANCH(87) 64  <| 85: EXACT <''>(88) failed... BRANCH failed... whilem: failed, trying continuation... 64  <| 89: NOTHING(90) 64  <| 90: SUCCEED(0) subpattern success... 64  <| 92: EXACT <'>(95) failed... BRANCH failed... Match failed Match failed. Freeing REx: "%n ^ (?: [^'%"\s~:/@\#|^&\[\]()\\{}]%n [^%"\s~:/"... 

我仍然更喜欢我的解决方案。 它更短。


编辑

看起来Java版本的步骤实际上比相同模式的Perl版本多100倍,我不知道为什么 – 除了Perl正则表达式编译器在优化方面比Java正则表达式编译器大约高出100倍。什么都不说,应该。

这是等效的Java程序。 我删除了主要锚点,以便我们可以正常循环。

 $ cat java.crap import java.util.regex.*; public class crap { public static void main(String[ ] argv) { String input = "'pão de açúcar itaucard mastercard platinum SUSTENTABILIDAD])"; String regex = "\n" + "(?: [^'\"\\s~:/@\\#|^&\\[\\]()\\\\{}] # alt 1: unquoted \n" + " [^\"\\s~:/@\\#|^&\\[\\]()\\\\{}] * \n" + " | \" (?: [^\"]++ | \"\" )++ \" # alt 2: double quoted \n" + " | ' (?: [^']++ | '' )++ ' # alt 3: single quoted \n" + ") \n" ; System.out.printf("Matching ‹%s› =~ qr{%s}x\n\n", input, regex); Pattern regcomp = Pattern.compile(regex, Pattern.COMMENTS); Matcher regexec = regcomp.matcher(input); int count; for (count = 0; regexec.find(); count++) { System.out.printf("Found match: ‹%s›\n", regexec.group()); } if (count == 0) { System.out.printf("Match failed.\n"); } } } 

运行时,会产生以下结果:

 $ javac -encoding UTF-8 crap.java && java -Dfile.encoding=UTF-8 crap Matching ‹'pão de açúcar itaucard mastercard platinum SUSTENTABILIDAD])› =~ qr{ (?: [^'"\s~:/@\#|^&\[\]()\\{}] # alt 1: unquoted [^"\s~:/@\#|^&\[\]()\\{}] * | " (?: [^"]++ | "" )++ " # alt 2: double quoted | ' (?: [^']++ | '' )++ ' # alt 3: single quoted ) }x Found match: ‹pão› Found match: ‹de› Found match: ‹açúcar› Found match: ‹itaucard› Found match: ‹mastercard› Found match: ‹platinum› Found match: ‹SUSTENTABILIDAD› 

正如你可以清楚地看到的那样,Java中的模式匹配有很多可以说的,绝对没有一个会通过便盆警察。 这只是屁股中的皇室痛苦。

我不得不承认这一点让我感到惊讶,但我在RegexBuddy中获得了相同的结果:它在经过一百万步之后就退出了。 我知道关于灾难性回溯的警告倾向于关注嵌套量词,但根据我的经验,交替至少是危险的。 事实上,如果我改变你的正则表达式的最后一部分:

 '(?:[^']+|'')+' 

……对此:

 '(?:[^']*(?:''[^']*)*)' 

……它只用了十一步就失败了。 这是Friedl的“展开循环”技术的一个例子,他将这种技术分解为:

 opening normal * ( special normal * ) * closing ' [^'] '' [^'] ' 

嵌套的星星是安全的,只要:

  1. specialnormal永远不能匹配相同的东西,
  2. special总是匹配至少一个字符,和
  3. special是primefaces的(必须只有一种方法可以匹配)。

然后正则表达式将无法与最小的回溯匹配,并且在没有回溯的情况下成功 。 另一方面,交替版本几乎可以保证回溯,并且在不可能匹配的情况下,随着目标字符串的长度增加,它会快速失控。 如果它在某些风格中没有过度回溯,那是因为它们具有专门用于解决这个问题的优化 – 到目前为止,这些问题很少。

有人可以解释为什么java的正则表达式引擎会在这个正则表达式上进入灾难性模式吗?

对于字符串:

 'pão de açúcar itaucard mastercard platinum SUSTENTABILIDADE]) 

似乎这部分正则表达式会成为问题所在:

 '(?:[^']+|'')+' 

匹配第一个'然后无法匹配结束' ,从而回溯嵌套量词的所有组合。

如果允许正则表达式回溯,它将回溯(失败时)。 使用primefaces组和/或所有格量词来防止这种情况。


顺便说一句,你不需要那个正则表达式中的大多数逃脱。 只有你(可能)需要在字符类( [] )中转义的是字符^-] 。 但通常你可以定位它们,以便它们也不需要被转义。 当然\和你要求的字符串仍然需要(双)转义。

 "^(?:[^]['\"\\s~:/@#|^&(){}\\\\][^][\"\s~:/@#|^&(){}\\\\]*|\"(?:[^\"]++|\"\")++\"|'(?:[^']++|'')++')"