JDK 1.4提供了内建的正则表达式支持,相应的,String类也提供了许多与正则表达式有关的方法,例如matches、replaceAll和split方法,为日常应用提供了许多便利。
在工作中,我发现,掌握一些不常用的技巧,往往可以极大地提高效率,以下是我总结的一点经验:
1.合理利用embedded flag。
某些时候,我们会遇到这样的情况:
要求匹配一个字符串,例如abcdefg,其中abc必须为小写,def大小写无所谓,g为小写。
此类问题,通常的做法是这样生成一个Pattern:
Pattern p = Pattern.compile("abcdefg", Pattern.CASE_INSENSITIVE);然而,此时这样做不能满足我们的要求,此时可借助embedded flag。
查阅JDK文档,在Special construts(non-capturing)中可以看到:
(?idmsux-idmsux) Nothing, but turns match flags on - off
(?idmsux-idmsux:X) X, as a non-capturing group with the given flags on - off
其中,各个字母的含义为
embedded flags
|
construction flags
|
meanings
|
i
|
Pattern.CASE_INSENSITIVE
|
Enables case-insensitive matching.
|
d
|
Pattern.UNIX_LINES
|
Enables Unix lines mode.
|
m
|
Pattern.MULTILINE
|
Enables multi line mode.
|
s
|
Pattern.DOTALL
|
Enables "." to match line terminators.
|
u
|
Pattern.UNICODE_CASE
|
Enables Unicode-aware case folding.
|
x
|
Pattern.COMMENTS
|
Permits white space and comments in the pattern.
|
---
|
Pattern.CANON_EQ
|
Enables canonical equivalence.
|
如此一来,我们就可写出符合要求的Pattern字符串:
Pattern p = Pattern.compile("abc(?i)def(?-i)g");在这个Pattern中,我们在def子串之前设置了CASE_INSENSITIVE为on,在之后将其设置为off,保证了abcg都为小写,而def可以为大小写。
有人可能会认为,不需要那么复杂,直接写
Pattern p = Pattern.compile("abc[dD][eE][fF]g");也可以解决问题。
确实,在这种情况下,两种办法的效果是一样。然而,有时候我们并不能事先确定字符串的内容(例如,需要从HTML代码中提取一个结点,也就是一对tag之间的内容,而tag是运行时动态决定的),此时只能借助match flag。
如果只修改了一次match flag,那么它的作用范围将从此处开始,一直到延伸到Pattern末尾,也就是说下面两个Pattern:
Pattern p1 = Pattern.compile("(?is)abcdefg");
Pattern p2 = Pattern.compile("abcdefg", PATTERN.CASE_INSENSITIVE | Pattern.DOTALL);
它们的作用是一样的。
match flag可以组合使用,例如(?is)表示大小写不敏感,同时.可以用来匹配换行符;同时,match flag也可以与外部的int flag同时使用,例如:
Pattern pattern = Pattern.compile( "(?-i:[A-Z])[A-Z]*" , Pattern.CASE_INSENSITIVE);
代表第一个字符为大写英文字母的单词,需要注意的是,此时第一个字符处于non-capturing group中,因此在group count时要格外注意。
一般情况下,我们都是调用String类的matches方法验证字符串的合法性,遇上复杂的情况,合理使用match flag会让我们事半功倍。
2.利用split
在引入split之前,如果需要分割字符串,必须使用StringTokenizer类,使用非常麻烦,而且缺乏灵活性(因为不容许分割的字符串有多种选择),JDK1.4引入了split方法,容许利用正则表达式分割字符串,非常方便。
以前我们也许需要这样写:
StringTokenizer st = new StringTokenizer("this is a test");
while (st.hasMoreTokens()) {
System.out.println(st.nextToken());
}
现在只需要这样:
String[] words = "this is a test".split("//s");
for(String s : words) {
System.out.println(s);
}
3.关于replaceAll
JDK1.4为String引入了replaceAll方法,可以方便地进行字符串替换
String source = "abcdefg";
String result = source.replaceAll("bcd", "BCD");
System.out.println("result is: " + result);得到的结果是
aBCDefg
需要注意的是,replaceAll的两个参数,都是正则表达式,按照JDK的文档
str.replaceAll(regex, repl)
等价于
Pattern.compile(regex).matcher(str).replaceAll(repl)
某些情况下,我们需要在替换的同时用到back reference,也就是说,被替换的内容是不确定的,而替换的结果又是与这些被替换内容相关的,此时就会发现使用正则表达式的好处了。
例如,某段文本中出现许多“班级-学号”(例如,172-04)的字符串,需要更改为“学号-班级”(04-172)的格式,我们可以在替换结果中利用$符号对被替换内容中的部分进行back reference:
String source = "172-04";
System.out.println(source.replaceAll("(//d+)-(//d+)", "$2-$1");
结果为
04-172
补充一点,正因为replaceAll的两个参数都是正则表达式,某些情况下可能抛出异常:
第一个参数regex中包含正则表达式中的escape character,例如 "abc//de"不会被作为 abc/de处理,而是作为 abc+某个数字+e,对此,我们可以用Quotation来解决——将 abc//de写作 "//Qabc//de//E",强制将这个String作为一个普通字符串来处理,不包含任何特殊意义的字符。
如果在第二个参数repl中包含了$1之类的字符,JVM会将其作为back reference来处理,此时会抛出异常,即使加上Quotation也无效:(
或许正是意识到了这个问题,JDK5为String类增加了replace方法,其使用方法类似replaceAll,但只进行普通的字符串替换,与正则表达式无关。
总之,灵活地运用replace和replaceAll,能够大大提高我们的工作效率。