正则表达式原理和优化
01. 正则匹配原理
最近工作中用到了很多正则匹配。才学习了正则表达式匹配原理以及优化。
1. 解析引擎眼中的字符串组成
对于字符串“DEF”而言,包括D、E、F三个字符和 0、1、2、3 四个数字位置:0D1E2F3,对于正则表达式而言所有源字符串,都有字符和位置。正则表达式会从0号位置,逐个去匹配的。
2. 占有字符和零宽度
正则表达式匹配过程中,如果子表达式匹配到的是字符内容,而非位置,并被保存到最终的匹配结果中,那么就认为这个子表达式是占有字符的;如果子表达式匹配的仅仅是位置,或者匹配的内容并不保存到最终的匹配结果中,那么就认为这个子表达式是零宽度的。占有字符是互斥的,零宽度是非互斥的。也就是一个字符,同一时间只能由一个子表达式匹配,而一个位置,却可以同时由多个零宽度的子表达式匹配。常见零宽字符有:^,(?=)等
3. 正则表达式匹配过程详解实例
我们掌握了上面几个概念,我们接下来分析下几个常见的解析过程。结合使用软件regexBuddy来分析。
Demo1:
源字符DEF,对应标记是:0D1E2F3,匹配正则表达式是:/DEF/
过程可以理解为:首先由正则表达式字符 /D/ 取得控制权,从位置0开始匹配,由 /D/ 来匹配“D”,匹配成功,控制权交给字符 /E/ ;由于“D”已被 /D/ 匹配,所以 /E/ 从位置1开始尝试匹配,由 /E/ 来匹配“E”,匹配成功,控制权交给 /F/ ;由 /F/ 来匹配“F”,匹配成功。
Demo2:
源字符DEF,对应标记是:0D1E2F3,匹配正则表达式是:/D\w+F/
过程可以理解为:首先由正则表达式字符 /D/ 取得控制权,从位置0开始匹配,由 /D/ 来匹配“D”,匹配成功,控制权交给字符 /\w+/ ;由于“D”已被 /D/ 匹配,所以 /\w+/ 从位置1开始尝试匹配,\w+贪婪模式,会记录一个备选状态,默认会匹配最长字符,直接匹配到EF,并且匹配成功,当前位置3了。并且把控制权交给 /F/ ;由 /F/ 匹配失败,\w+匹配会回溯一位,当前位置变成2。并把控制权交个/F/,由/F/匹配字符F成功。因此\w+这里匹配E字符,匹配完成!
参考《http://www.jb51.net/article/73403.htm》
02. 正则表达式性能优化方法
其实主要是它的“回溯”,减少“回溯”次数(减少循环查找同一个字符次数),是提高性能的主要方法。
思路:
- 使用正确的边界匹配器(^、$、\b、\B等),限定搜索字符串位置
- 尽量不适用通配符".";字符使用具体的元字符、字符类(\d、\w、\s等)(推荐)
- 使用正确的量词(+、*、?、{n,m}),如果能够限定长度,匹配最佳
- 使用非捕获组、原子组,减少没有必要的字匹配捕获用(?😃,减少内存。
附上正则表达式基础:
《https://deerchao.net/tutorials/regex/regex.htm#lookaround》
还有查看分析的软件:RegexBuddy
匹配实例
《http://www.jb51.net/article/85895.htm》
《http://blog.csdn.net/king_xing/article/details/52771465》
03. 代码优化
Java中一般是使用Patter和Matcher进行正则匹配。
之前写的方法:
/**
* 匹配str中的正则结果
*
* @param regEx 正则式String
* @param str 待匹配的源字符串
* @return 返回匹配的捕获组1
*/
public static String regExMatcher(String regEx, String str) {
Pattern pattern = Pattern.compile(regEx);
Matcher matcher = pattern.matcher(str);
return matcher.find() ? matcher.group(1) : "";
}
上面的优化都是针对正则表达式本身的,但是其实正则的损耗还来源于Pattern.compile编译的过程。在这个过程中建立语法分析树。
因此,对于多次执行的相同pattern的匹配工具类,应该将Pattern.compile(regEx);抽取成成员变量,在类中初始化。
Ps: Pattern和String一样,是不可变类,即编译后该对象的内容就确定了,无法修改,只读的,Pattern必然是线程安全的,并发使用没有安全隐患;
在matcher()方法中也并不能对其修改
因此,改为:
/**
* 匹配str中的正则结果
*
* @param regExPattern 已编译好的Pattern模式
* @param str 待匹配的源字符串
* @return 返回匹配的捕获组1
*/
public static String regExMatcher(Pattern regExPattern, String str) {
Matcher matcher = regExPattern.matcher(str);
return matcher.find() ? matcher.group(1) : "";
}