正则表达式引起的血案

1.问题及背景

一个需求,在一段字符串中识别是否有邮箱. 因而写了一个正则表达式html

private final static Pattern REGEX_EMAIL_PATTEN = Pattern.compile("([a-z0-9A-Z]+[-|\\.]?)+[a-z0-9A-Z]@([a-z0-9A-Z]+(-[a-z0-9A-Z]+)?\\.)+([a-zA-Z]*)?");
  
  
    public static String parseEmail(String str){
        if (StringUtil.isBlank(str)) {
            return null;
        }
        Matcher m = REGEX_EMAIL_PATTEN.matcher(str);
        if (m.find()) {
            int start = m.start();
            int end = m.end();
            return str.substring(start,end);
        }
        return null;
    }

而后作了几个简单的测试,嗯,都经过了.最终上线。 没多久运维通知服务cpu load很高,堆栈信息拉出来一看... 堆栈信息见本文末尾. 定位到了 parseEmail(String str) 这个方法.根据时间定位,拉取线上请求日志,发现方法入参是这样的:java

http://mail.1etrip.com/OK/controller/journey/apv/toApprove.html?param=20855793060AD450E779B0CAC0CEB570B571740F86C17F660B12EDC2ED0FFDCA  2018-10-19

这样的正则表达式

www.benke168.com/Admin/Wechatlogin/index/U/cbmeVnwkYeW1l6y8aWQ9NTMyL3Bhc3N3b3JkPWUxMGFkYzM5NDliYTU5YWJiZTU2ZTA1N2YyMGY4ODNlL3VzZ
XJuYW1lPTEzOTUyNDI1NTk1.html

本地运行一下,果真跑不出来结果. 搜索引擎查查看.算法

Java regex正则表达式相似死循环问题,详见:http://bugs.sun.com/bugdatabase/view_bug.do?bug_id=6988218 这个问题实际上是因为正则表达式很复杂时,java regex复杂度太高(复杂度成指数级),致使相似死循环的问题。less

那么这个问题怎么解决...修改正则表达式,简单粗暴, 匹配 x@y.z这样的字符串就行了。x长度 1,y的长度1~30 ,z的长度1~10.运维

private final static Pattern REGEX_EMAIL_PATTEN = Pattern.compile("([a-z0-9A-Z])@([a-z0-9A-Z]){1,30}\\.([a-z0-9A-Z]){1,10}");
修改后的测试

用9月20日当天1000w条历史数据作单元测试. 最终结果:耗时大于10毫秒的有26条数据.最大的有36毫秒。 耗时最大的数据是这样的:工具

1 172.25.50.202-DB: F:\\backup\\236\\DB\\2018/9/19 19:39:29本次备份完成文件: CheckOperationLastTime_backup_2018_09_03_092647_0992470.bak CheckOperationLastTime_backup_2018_09_03_130001_6685014.bak CheckOperationLastTime_backup_2018_09_19_130001_5866855.bak office_wx_backup_2018_09_03_092647_1178543.bak office_wx_backup_2018_09_03_130001_6841585.bak office_wx_backup_2018_09_19_130001_6023100.bak UMDataBase_backup_2018_09_03_092647_1178543.bak UMDataBase_backup_2018_09_03_130001_6997541.bak UMDataBase_backup_2018_09_19_130001_6023100.bak WXGZ_backup_2018_09_03_092647_1647407.bak WXGZ_backup_2018_09_03_130001_7466590.bak WXGZ_backup_2018_09_19_130001_6491867.bak WX_CAM_backup_2018_09_03_092647_1334902.bak WX_CAM_backup_2018_09_03_130001_6997541.bak WX_CAM_backup_2018_09_19_130001_6179345.bak WX_GDZC_backup_2018_09_03_092647_1334902.bak WX_GDZC_backup_2018_09_03_130001_7153797.bak WX_GDZC_backup_2018_09_19_130001_6179345.bak WX_GWGL_backup_2018_09_03_092647_1491175.bak WX_GWGL_backup_2018_09_03_130001_7153797.bak WX_GWGL_backup_2018_09_19_130001_6335584.bak WX_SWTJ_backup_2018_09_03_092647_1491175.bak WX_SWTJ_backup_2018_09_03_130001_7310320.bak WX_SWTJ_backup_2018_09_19_130001_6335584.bak WX_UMDataBase_backup_2018_09_03_092647_1647407.bak WX_UMDataBase_backup_2018_09_03_130001_7310320.bak WX_UMDataBase_backup_2018_09_19_130001_6491867.bak

emmmm...基本符合预期了.oop

2.正则表达式算法分析

·常见正则表达式引擎

参考:正则表达式匹配原理单元测试

引擎 区别点
DFA<br> Deterministic finite automaton 肯定型有穷自动机 DFA引擎它们不要求回溯(并所以它们永远不测试相同的字符两次),因此匹配速度快!DFA引擎还能够匹配最长的可能的字符串。不过DFA引擎只包含有限的状态,因此它不能匹配具备反向引用的模式,还不能够捕获子表达式。表明性有:awk,egrep,flex,lex,MySQL,Procmail
NFA<br>Non-deterministic finite automaton 非肯定型有穷自动机,又分为传统NFA,Posix NFA 传统的NFA引擎运行所谓的“贪婪的”匹配回溯算法(longest-leftmost),以指定顺序测试正则表达式的全部可能的扩展并接受第一个匹配项。传统的NFA回溯能够访问彻底相同的状态屡次,在最坏状况下,它的执行速度可能很是慢,但它支持子匹配。表明性有:GNU Emacs,Java,ergp,less,more,.NET语言,PCRE library,Perl,PHP,Python,Ruby,sed,vi等,通常高级语言都采用该模式。

DFA以字符串字符,逐个在正则表达式匹配查找,而NFA以正则表达式为主,在字符串中逐一查找。尽管速度慢,可是对操做者来讲更简单,所以应用更普遍。测试

·解析引擎眼中的字符串组成

对于字符串“DEF”而言,包括D、E、F三个字符和 0、一、二、3 四个数字位置:0D1E2F3,对于正则表达式而言全部源字符串,都有字符和位置。正则表达式会从0号位置,逐个去匹配的。

·占有字符和零宽度

正则表达式匹配过程当中,若是子表达式匹配到的是字符内容,而非位置,并被保存到最终的匹配结果中,那么就认为这个子表达式是占有字符的;若是子表达式匹配的仅仅是位置,或者匹配的内容并不保存到最终的匹配结果中,那么就认为这个子表达式是零宽度的。占有字符是互斥的,零宽度是非互斥的。也就是一个字符,同一时间只能由一个子表达式匹配,而一个位置,却能够同时由多个零宽度的子表达式匹配。常见零宽字符有:^,(?=)等

工具推荐

regex101.zip 下载

注意事项:

慎用 "+,*",用 {1,30}这样的替代. +匹配1到无穷次. *匹配0到无穷次. {1,30}匹配1到30次 集合本次案例分析:

([a-z0-9A-Z]+[-|\\.]?)+[a-z0-9A-Z]@([a-z0-9A-Z]+(-[a-z0-9A-Z]+)?\\.)+([a-zA-Z]*)? 致使匹配长字符串是匹配复杂度指数级增长

附录:堆栈信息

[7] Busy(11.4%) thread(23901/0x5d5d) stack of java process(23087) under user(admin):
"catalina-exec-1" #381 daemon prio=5 os_prio=0 tid=0x00007facd4088800 nid=0x5d5d runnable [0x00007fad1079b000]
   java.lang.Thread.State: RUNNABLE
        at java.util.regex.Pattern$Curly.match(Pattern.java:4227)
        at java.util.regex.Pattern$GroupHead.match(Pattern.java:4658)
        at java.util.regex.Pattern$Loop.match(Pattern.java:4785)
        at java.util.regex.Pattern$GroupTail.match(Pattern.java:4717)
        at java.util.regex.Pattern$Ques.match(Pattern.java:4182)
        at java.util.regex.Pattern$Curly.match0(Pattern.java:4272)
        at java.util.regex.Pattern$Curly.match(Pattern.java:4234)
        at java.util.regex.Pattern$GroupHead.match(Pattern.java:4658)
        at java.util.regex.Pattern$Loop.match(Pattern.java:4785)
        at java.util.regex.Pattern$GroupTail.match(Pattern.java:4717)
        at java.util.regex.Pattern$Ques.match(Pattern.java:4182)
        at java.util.regex.Pattern$Curly.match0(Pattern.java:4272)
        at java.util.regex.Pattern$Curly.match(Pattern.java:4234)
        at java.util.regex.Pattern$GroupHead.match(Pattern.java:4658)
        at java.util.regex.Pattern$Loop.match(Pattern.java:4785)
        at java.util.regex.Pattern$GroupTail.match(Pattern.java:4717)
        at java.util.regex.Pattern$Ques.match(Pattern.java:4182)
        at java.util.regex.Pattern$Curly.match0(Pattern.java:4272)
        at java.util.regex.Pattern$Curly.match(Pattern.java:4234)
        at java.util.regex.Pattern$GroupHead.match(Pattern.java:4658)
....
  at java.util.regex.Pattern$Curly.match0(Pattern.java:4272)
        at java.util.regex.Pattern$Curly.match(Pattern.java:4234)
        at java.util.regex.Pattern$GroupHead.match(Pattern.java:4658)
        at java.util.regex.Pattern$Loop.matchInit(Pattern.java:4801)
        at java.util.regex.Pattern$Prolog.match(Pattern.java:4741)
        at java.util.regex.Pattern$Start.match(Pattern.java:3461)
        at java.util.regex.Matcher.search(Matcher.java:1248)
        at java.util.regex.Matcher.find(Matcher.java:637)
        at com.weike.commons.util.RegexUtil.parseEmail(RegexUtil.java:260)
        at com.taovip.v2.action.SmsAction.lambda$needSmsRecheck$5(SmsAction.java:4670)
        at com.taovip.v2.action.SmsAction$$Lambda$10/40486007.test(Unknown Source)
        at java.util.stream.ReferencePipeline$2$1.accept(ReferencePipeline.java:174)
        at java.util.stream.ReferencePipeline$2$1.accept(ReferencePipeline.java:175)
        at java.util.stream.ReferencePipeline$2$1.accept(ReferencePipeline.java:175)
        at java.util.HashMap$ValueSpliterator.tryAdvance(HashMap.java:1633)
        at java.util.stream.ReferencePipeline.forEachWithCancel(ReferencePipeline.java:126)
        at java.util.stream.AbstractPipeline.copyIntoWithCancel(AbstractPipeline.java:498)
        at java.util.stream.AbstractPipeline.copyInto(AbstractPipeline.java:485)
        at java.util.stream.AbstractPipeline.wrapAndCopyInto(AbstractPipeline.java:471)
        at java.util.stream.MatchOps$MatchOp.evaluateSequential(MatchOps.java:230)
        at java.util.stream.MatchOps$MatchOp.evaluateSequential(MatchOps.java:196)
        at java.util.stream.AbstractPipeline.evaluate(AbstractPipeline.java:234)
        at java.util.stream.ReferencePipeline.anyMatch(ReferencePipeline.java:449)
相关文章
相关标签/搜索