修改 Pattern代码使 Java 正则表达式的group名称支持下划线 '_'
为什么
由于工作是做数据ETL的,很多时候会使用到正则对数据进行提取,但是java的正则中的groupname不支持'_',官方的文档中是这样的:
Group name
A capturing group can also be assigned a "name", a named-capturing group, and then be back-referenced later by the "name". Group names are composed of the following characters. The first character must be a letter.
The uppercase letters 'A' through 'Z' ('\u0041' through '\u005a'),
The lowercase letters 'a' through 'z' ('\u0061' through '\u007a'),
The digits '0' through '9' ('\u0030' through '\u0039'),
A named-capturing group is still numbered as described in Group number.The captured input associated with a group is always the subsequence that the group most recently matched. If a group is evaluated a second time because of quantification then its previously-captured value, if any, will be retained if the second evaluation fails. Matching the string "aba" against the expression (a(b)?)+, for example, leaves group two set to "b". All captured input is discarded at the beginning of each match.
Groups beginning with (? are either pure, non-capturing groups that do not capture text and do not count towards the group total, or named-capturing group.
可以看到,只支持大写字母A-Z、小写字母a-z、数字0-9
查找源代码
在java.util.regex.Pattern类的以下源码中(jdk1.8.141是2789行)有下面这个方法:
/**
* Parses and returns the name of a "named capturing group", the trailing
* ">" is consumed after parsing.
*/
private String groupname(int ch) {
StringBuilder sb = new StringBuilder();
sb.append(Character.toChars(ch));
while (ASCII.isLower(ch=read()) || ASCII.isUpper(ch) ||
ASCII.isDigit(ch)) {
sb.append(Character.toChars(ch));
}
if (sb.length() == 0)
throw error("named capturing group has 0 length name");
if (ch != '>')
throw error("named capturing group is missing trailing '>'");
return sb.toString();
}
可以看到,源代码中对groupname的提取是一个while循环,当读取到的字符是小写字母(ASCII.isLower)、大写字母(ASCII.isUpper)、数字(ASCII.isDigit)的时候,会把这个字符添加到StringBuilder中,然后读取下个字符,知道不满足这个条件。
修改源代码
好,现在知道是这个原因了,怎么进行修改呢?
有很多人说不要修改大神写的代码,但是没办法。
由于不支持'_', 给工作带来挺多其它麻烦的,比如数据库中的字段名有'_',如果正则组不支持下划线的话,就需要一个正则组名和列名的映射关系,或者不用正则组名,使用正则组下标0,1,2...来映射。比较繁琐。
修改其实很简单,由于Pattern这个类在源代码中定义为final的,没法直接继承然后overwrite这个方法,就只能在自己的项目下新建一个regex包,将java.util.regex包的类都copy出来,总共是6个
修改Pattern的上述方法,'_'这个字符在ASCII中是95,所以添加一个判断就可以了:
private String groupname(int ch) {
StringBuilder sb = new StringBuilder();
sb.append(Character.toChars(ch));
//TODO 增加了ch==95这个条件来支持正则组名支持下划线('_'),
//源码为java.util.regex.Pattern的2793行
while (ASCII.isLower(ch=read()) || ASCII.isUpper(ch) ||
ASCII.isDigit(ch) || ch == 95) {
sb.append(Character.toChars(ch));
}
if (sb.length() == 0)
throw error("named capturing group has 0 length name");
if (ch != '>')
throw error("named capturing group is missing trailing '>'");
return sb.toString();
}
这样就可以使用我们自己Pattern类了,最后成功运行
public class MyTest {
public static void main(String[] args) {
Pattern pattern = Pattern.compile("\\s\\|\\s(?<my_name>worker_\\d+)\\s\\|");
Matcher matcher = pattern.matcher("2017-02-14 23:58:04 | worker_10 | [ATMP05]");
if (matcher.find()){
//打印出来是"worker_10"
System.out.println(matcher.group("my_name"));
}
}
}
最后,这个源码值改了一小部分,但是却让工作轻松了
当然,这样改是否会影响到其它东西需要时间的检验。