StackOverflowError:正则表达式栈溢出错误
Some people, when confronted with a problem, think "I know, I'll use regular expressions." Now they have two problems.
“ 如果你有一个问题,用正则表达式解决,那么你现在就有两个问题了。”
问题
最近碰巧遇到了一个使用正则表达式错误!!
我们系统是基于Java语言开发的,其中使用的是JDK8自带的java.util.regex.Pattern类实现正则表达式匹配的。
系统中有一个正则表达式如下。
\{(.|\n)*?\}
其目的是用来区分Json风格字符串与其它字符串的。
但在实际使用中,当传入一个较长的Json字符串时,却发生了如下的错误。
Exception in thread "main" java.lang.StackOverflowError at java.util.regex.Pattern$LazyLoop.match(Pattern.java:4845) at java.util.regex.Pattern$GroupTail.match(Pattern.java:4719) at java.util.regex.Pattern$BranchConn.match(Pattern.java:4570) at java.util.regex.Pattern$CharProperty.match(Pattern.java:3779) at java.util.regex.Pattern$Branch.match(Pattern.java:4606) at java.util.regex.Pattern$GroupHead.match(Pattern.java:4660) at java.util.regex.Pattern$LazyLoop.match(Pattern.java:4849) at java.util.regex.Pattern$GroupTail.match(Pattern.java:4719) at java.util.regex.Pattern$BranchConn.match(Pattern.java:4570) at java.util.regex.Pattern$CharProperty.match(Pattern.java:3779) at java.util.regex.Pattern$Branch.match(Pattern.java:4606) at java.util.regex.Pattern$GroupHead.match(Pattern.java:4660) at java.util.regex.Pattern$LazyLoop.match(Pattern.java:4849) at java.util.regex.Pattern$GroupTail.match(Pattern.java:4719) at java.util.regex.Pattern$BranchConn.match(Pattern.java:4570) at java.util.regex.Pattern$CharProperty.match(Pattern.java:3779) at java.util.regex.Pattern$Branch.match(Pattern.java:4606) at java.util.regex.Pattern$GroupHead.match(Pattern.java:4660) ... ...(非常多行)
分析
通过观察,我们知道这是一个虚拟机 栈溢出 错误。
Java语言中每个线程(Thread)的栈大小是有限制的,每一次函数调用都会生成一个栈帧(Frame),占用一定的栈空间,当栈空间被消耗完虚拟机就会报出StackOverflowError。
再仔细地观察,发现栈信息中显示调用层次非常多,而且类名与代码行重复出现,这意味着栈帧是由递归调用产生的。
到这里,问题发生的原因己经基本可以确定了。
在Java中正则表达式的产生了递归调用,而传入的长Json字符串导致函数调用层次太多,超过虚拟机预设的栈空间,因此,出现了StackOverflowError。
追根溯源
然而,以上的解释纯粹是现象+经验的推断,显然不能满足我的好奇心。
所以,我略略又研究了一下Java正则表达式部分功能的实现。
Java语言在做正则表达式匹配时,首先要进行编译。
使用java.util.regex.Pattern类的compile方法,编译的过程相当于把正则表达式
\{(.|\n)*?\}
转换类似下图的数据结构。
实际编译后的对象结构更复杂些,如下图。
上图每个节点的类型都继承自Pattern$Node类,实现了match方法(即使没有重写方法,也有默认实现)。
当进行字付串匹配时,java.util.regex.Matcher的matches方法会调用第一个节点的match方法,继而形成递归调用链。
正如上面编译后的表达式,有一个以LazyLoop为启始的循环引用,当字符串的每个字符进行匹配时,都产生如下的栈结构。
at java.util.regex.Pattern$GroupTail.match(Pattern.java:4719) at java.util.regex.Pattern$BranchConn.match(Pattern.java:4570) at java.util.regex.Pattern$CharProperty.match(Pattern.java:3779) at java.util.regex.Pattern$Branch.match(Pattern.java:4606) at java.util.regex.Pattern$GroupHead.match(Pattern.java:4660) at java.util.regex.Pattern$LazyLoop.match(Pattern.java:4849)
由于传入的字符串很长,因此递归调用的层次太多,很快便耗尽线程的栈空间(可以用-Xss配置),接着虚拟机就报出了StackOverflowError错误。
改进
知道了问题,我们着手进行改进。
修改正则表达式如下。
\{[\w\W]*\}
这样,编译后的表达式结构变成为。
而在Pattern$Curly中的match方法使用的是while循环查找而非递归,因此,不会造成栈溢出。
总结
关于正则表达式及其优化,讲解的比较好的链接如下,这里不再赘述。
其中有一点与我这个场景是有关系的。
留意选择(Beware of alternation)
类似“(X|Y|Z)”的正则表达式有降低速度的坏名声,所以要多留心。首先,考虑选择的顺序,那么要将比较常用的选择项放在前面,因此它们可以较快被匹配。另外,尝试提取共用模式;例如将“(abcd|abef)”替换为“ab(cd|ef)”。后者匹配速度较快,因为NFA会尝试匹配ab,如果没有找到就不再尝试任何选择项。……
这一点其实不难理解,表达式编译后 (X|Y|Z) 其实是一个数组,从前到后依次进行匹配。
因此,在Java中写正则表达式最重要的还是:
减少分组与嵌套
如果你实际并不需要获取一个分组内的文本,那么就使用非捕获分组。例如使用“(?:X)”代替“(X)”。

低调大师中文资讯倾力打造互联网数据资讯、行业资源、电子商务、移动互联网、网络营销平台。
持续更新报道IT业界、互联网、市场资讯、驱动更新,是最及时权威的产业资讯及硬件资讯报道平台。
转载内容版权归作者及来源网站所有,本站原创内容转载请注明来源。
- 上一篇
【译】如何更好的使用javascript数组
赶紧阅读读此文,我保证,在过去的几个月里我,我确定我在数组问题上犯过4次错误。于是我写下这篇文章,阅读这篇文章可以让你更准确的使用javascript数组的一些方法 使用Array.includes替代 Array.indexOf “如果你在数组中搜索某个元素,那么请使用Array.indexOf” ,我记得在学习javascript时看到过这个句子,毫无疑问,这句话很对。 MDN文档上这样描述 rray.indexOf“返回第一个被搜索到的元素的下标(索引)” ,所以如果你想要搜索某个元素的下标,那么Array.indexOf可以很好的解决。 但是,如果我们想查看一个数组中是否包涵某个元素该如何做呢。就像yes/no这样的问题,也就是布尔值。这里我们推荐使用返回布尔值的Array.includes方法。 const persons = ["jay","leinov","jj","nico"]; console.log(persons.indexOf("leinov")); // 1 console.log(persons.indexOf("beyond")); // -1 conso...
- 下一篇
php发邮件foxmail标题显示乱码问题解决方案
之前在大神那里找了一个SMTP发送邮件的源码,应用之后是可以发送邮件了,然后web查看邮件也正常,但是foxmail客户端一直显示标题乱码,正文是正常的,几经搜索之后,终于发现原来邮件是base64编码的,所有需要将标题经过编码之后才发送,测试之后,果然正常了。 $mailsubject = "=?UTF-8?B?".base64_encode($mailsubject)."?="; //标题转码,防止foxmail客户端乱码 详细代码如下,mail.php是借用大神的,但是忘记在哪下载了的,如需要下线,请联系。 mail.php <?php class smtp { var $smtp_port; var $time_out; var $host_name; var $log_file; var $relay_host; var $debug; var $auth; var $user; var $pass; var $sock; function smtp($relay_host = "", $smtp_port = 25,$auth = false,$user,$pass)...
相关文章
文章评论
共有0条评论来说两句吧...