尝试在java中使用正则表达式时堆栈溢出

Posted

技术标签:

【中文标题】尝试在java中使用正则表达式时堆栈溢出【英文标题】:Stack overflow when trying to use regex in java 【发布时间】:2019-01-13 20:35:44 【问题描述】:

我已经阅读了一些关于如何优化正则表达式的文章,但没有一个答案(较少的组,使用 X,Y 而不是 *)似乎阻止我的正则表达式出现堆栈溢出错误。

我正在尝试通过文件进行动态搜索。假设我在一个非常大(2-4 mb)的文件中搜索“我打赌你找不到我”。我的正则表达式生成器会生成正则表达式:

i(?:.|\s)*?bet(?:.|\s)*?you(?:.|\s)*?cannot(?:.|\s)*?find(?:.|\s)*?me

这个正则表达式的想法是,无论单词之间有什么字符或空格,它都能找到准确的短语。但是,当我尝试使用时:

Pattern p = Pattern.compile(generatedRegex, Pattern.MULTILINE);
Matcher m = p.matcher(fileContentsAsString);
while (m.find()) 
System.out.println(m.group())

我收到堆栈溢出错误。我知道正则表达式使用递归,但似乎这不是正则表达式的坏处。有什么办法可以优化这个正则表达式吗?谢谢!

答案:

Pattern p = Pattern.compile("i(?:.*)bet(?:.*)you(?:.*)cannot(?:.*)find(?:.*?)me", Pattern.DOTALL);

是我最终使用的模式/正则表达式。似乎很快并且不再出现堆栈溢出异常

【问题讨论】:

(?:.|\s) 的目的是什么?是因为. 与行终止符不匹配,而您想要它吗?如果是这样,请使用DOTALL 模式,即(?s),如下所示:(?s)i.*?bet.*?you.*?cannot.*?find.*?me 你试过用贪婪的*代替不情愿的限定符*? 另外,由于您的正则表达式没有^$MULTILINE 模式毫无意义。 在使用 dotall 后,删除了不必要的不​​情愿限定符,并去掉了不必要的 MULTILINE 选项,它确实运行得非常快。没有更多的堆栈溢出!感谢 cmets! @BlahMclean 在每个单词周围添加捕获组,然后打印每个单词的起始索引,而不是整个匹配的文本。 【参考方案1】:

我认为由于你不情愿的限定词(*?),你得到了很多回溯。防止回溯的一种方法是使用原子分组(?>X) 和/或所有格限定符(*+)

根据 cmets,您还希望仅捕获最接近“下注”的“i”以减少整体匹配的长度。由于您希望获得与其余单词最接近的“i”,因此在我为单词 2 添加负前瞻的地方,您还可以在其旁边为单词 1 放置负前瞻。换句话说,(?!bet) 将变为(?!i)(?!bet)(?!i|bet)。我已编辑以下代码以包含此要求。

String fileContentsAsString = "ii ... bet ... you, ibetyouyou";
String regex = "i(?>(?!i|bet).)*+bet(?>(?!you).)*+you";
Pattern p = Pattern.compile(regex, Pattern.DOTALL);
Matcher m = p.matcher(fileContentsAsString);
while (m.find()) 
    System.out.println(m.group());

输出:

我……打赌……你

我是你

解释 (source):

“不情愿量词的工作方式是,每次它应该尝试匹配时,它首先尝试让正则表达式的下一部分匹配。因此它在每次迭代开始时有效地进行前瞻,这可以变得相当昂贵,尤其是当量化部分每次迭代只匹配一个字符时,比如 .*?"

【讨论】:

在我看来,除了通缉组之外,你得到了一切,如果我错了,请纠正我。 @S.Klumpers 不正确。 @op 中的 (?:.|\s) 都是非捕获组。他只捕获group(),这是总匹配 这是真的,那些是非捕获组,因为我真的不介意单词之间的内容。 @PatrickParker 我改用 dotall 并去掉了不必要的不​​情愿限定符,从而消除了我的堆栈溢出异常。在这一点上,很难说您建议的其他更改会在多大程度上提高性能,但我非常感谢您对我进行原子分组方面的教育! 使用上述解决方案后,我的正则表达式现在在其余模式之前捕获第一个“i”(这可能是我文件的很大一部分)。我怎样才能告诉正则表达式得到最接近其余单词的“i”? @BlahMclean 在这种情况下,然后在我为第二个单词添加负前瞻的地方,你也会在它旁边为单词一添加一个负前瞻。换句话说,(?!bet) 将变为 (?!i|bet)

以上是关于尝试在java中使用正则表达式时堆栈溢出的主要内容,如果未能解决你的问题,请参考以下文章

正则表达式全名验证

正则表达式 —— 回溯陷阱

将 po box javascript 正则表达式转换为 c# 正则表达式

正则表达式和 CPU 100%有什么故事?

藏在正则表达式里的陷阱,竟让CPU飙升到100%!

正则表达式引发的惨案