Java中带捕获组的正向预查正则分词邮箱是否会触发灾难性回溯?
邮箱分词正则的灾难性回溯风险及替代方案
一、当前正则的回溯风险分析
你的正则表达式确实存在触发灾难性回溯的可能,同时还有语法错误:
- 字符类语法问题:
[\@|\.|...]里的|会被当成普通匹配字符,导致正则会意外匹配|符号,这显然不是你的本意。字符类里不需要用|分隔符号,直接罗列目标符号即可。 - 贪婪匹配的回溯隐患:正则中的
(.+)是贪婪匹配,加上正向预查是零宽断言,会在字符串的每个位置尝试匹配。处理长邮箱时,(.+)会先匹配到字符串末尾,再逐步回溯以满足前面的特殊符号匹配,这种回溯次数会随字符串长度呈指数级增长,极端情况下会直接导致栈溢出或程序卡死。
比如邮箱是a.b.c.d.e.f@x.y.z.w.v.u时,正则会在每个字符位置触发匹配,每次(.+)都要从末尾反复回溯,性能消耗会非常夸张。
二、替代解决方案
你的需求是提取所有特殊符号(@、.等)开头的后缀,以及符号后的纯字符后缀,完全可以避开正则的回溯问题,用两种更可靠的方式实现:
方案1:修复并优化正则
如果一定要用正则,先修正字符类错误,同时减少回溯次数:
(?=([@.!#$%&'*+/=?^_`{|}~](.*)))
关键修改点:
- 移除字符类中的多余
|,只保留需要匹配的特殊符号。 - 把贪婪的
(.+)换成(.*),避免不必要的回溯(.*匹配空字符串时无需回溯)。
但即使优化后,正则仍会在每个字符串位置尝试匹配,性能还是不如纯字符串操作,更推荐方案2。
方案2:纯字符串遍历(推荐)
直接遍历邮箱字符串,定位所有特殊符号的位置,然后生成对应的后缀,完全规避正则的回溯问题,逻辑也更清晰:
import java.util.ArrayList; import java.util.List; public class EmailTokenizer { public static List<String> tokenizeEmail(String email) { List<String> tokens = new ArrayList<>(); // 定义需要触发分词的特殊符号集合 String triggerChars = "@.!#$%&'*+/=?^_`{|}~"; for (int i = 0; i < email.length(); i++) { char currentChar = email.charAt(i); if (triggerChars.indexOf(currentChar) != -1) { // 添加「符号+后缀」的分词结果 tokens.add(email.substring(i)); // 如果不是最后一个字符,添加「符号后纯字符后缀」 if (i < email.length() - 1) { tokens.add(email.substring(i + 1)); } } } return tokens; } public static void main(String[] args) { String testEmail = "John.doe@abc.co.in"; tokenizeEmail(testEmail).forEach(System.out::println); } }
运行后输出:
.doe@abc.co.in doe@abc.co.in @abc.co.in abc.co.in .co.in co.in .in in
和你的预期结果完全匹配(你写的两个doe@abc.co.in应该是笔误,实际第一个是.doe@abc.co.in)。
这种方案的优势:
- 性能拉满,没有正则的回溯开销,处理任意长度的邮箱都不会有性能问题。
- 逻辑直观,容易调试和修改,比如要增减触发符号直接修改
triggerChars即可。
三、总结
当前正则的语法错误和贪婪匹配逻辑,必然会导致灾难性回溯问题。优先选择纯字符串遍历的方案,既高效又能精准满足需求。
内容的提问来源于stack exchange,提问作者Adhish Kandhan
相关产品推荐
相关产品推荐

