尝试在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中使用正则表达式时堆栈溢出的主要内容,如果未能解决你的问题,请参考以下文章