正则表达式是JDK 1.4的新功能但是对sed和awk这样的Unix的标准实用工具以及Python Perl之类的语言来讲它早就已经成为其不可或缺的组成部分了(有人甚至认为它还是Perl能大获成功的最主要的原因)。单从技术角度来讲正则表达式只是一种处理字符串的工具(过去Java这个任务是交由String StringBuffer以及StringTokenizer处理的)但是它常常和I/O一起使用所以放到这里来讲也不算太离题吧。 [66]
正则表达式是一种功能强大但又非常灵活的文本处理工具。它能让你用编程的方式来描述复杂的文本模式然后在字符串里把它找出来。一旦你找到了这种模式你就能随心所欲地处理这些文本了。虽然初看起来正则表达式的语法有点让人望而生畏但它提供了一种精练的动态语言使我们能用一种通用的方式来解决各种字符串的问题包括匹配选择编辑以及校验。
创建正则表达式
你可以从比较简单的东西入手学习正则表达式。要想全面地掌握怎样构建正则表达式可以去看JDK文档的java.uti l .regex的Pattern类的文档。
字符
B字符B
\xhh 16进制值0xhh所表示的字符
\uhhhh 16进制值0xhhhh所表示的Unicode字符
\tTab
\n换行符
\r回车符
\f换页符
\e Escape
正则表达式的强大体现在它能定义字符集(character class)。下面是一些最常见的字符集及其定义的方式此外还有一些预定义的字符集
字符集
.表示任意一个字符
[abc]表示字符a b c中的任意一个(与a|b|c相同)
[^abc] 除a b c之外的任意一个字符(否定)
[a-zA-Z]从a到z或A到Z当中的任意一个字符(范围)
[abc[hij]] a,b,c,h, i,j中的任意一个字符(与a|b|c|h| i |j相同)(并集)
[a-z&&[hij]] h, i,j中的一个(交集)
\s空格字符(空格键, tab,换行,换页,回车)
\S非空格字符([^\s])
\d一个数字也就是[0-9]
\D一个非数字的字符也就是[^0-9]
\w一个单词字符(word character) 即[a-zA-Z_0-9]
\W一个非单词的字符 [^\w]
如果你用过其它语言的正则表达式那么你一眼就能看出反斜杠的与众不同。在其它语
言里 "\\"的意思是"我只是要在正则表达式里插入一个反斜杠。没什么特别的意思。 "但是在Java里 "\\"的意思是"我要插入一个正则表达式的反斜杠所以跟在它后面的那个字符的意思就变了。 "举例来说如果你想表示一个或更多的"单词字符"那么这个正则表达式就应该是"\\w+"。如果你要插入一个反斜杠那就得用"\\\\"。不过像换行跳格之类的还是只用一根反斜杠 "\n\t"。
这里只给你讲一个例子你应该JDK文档的java.uti l .regex.Pattern加到收藏夹里这样就能很容易地找到各种正则表达式的模式了。
逻辑运算符
XY X后面跟着Y
X|YX或Y
(X)一个"要匹配的组(capturing group)".以后可以用\i来表示第i个被匹配的组。边界匹配符
^一行的开始
$一行的结尾
\b一个单词的边界
\B一个非单词的边界
\G前一个匹配的结束
举一个具体一些的例子。下面这些正则表达式都是合法的而且都能匹配"Rudolph"Rudolph
[rR]udolph
[rR][aeiou][a-z]ol .*
R.*
数量表示符
"数量表示符(quantifier)"的作用是定义模式应该匹配多少个字符。
Greedy(贪婪的) 除非另有表示否则数量表示符都是greedy的。Greedy的表达式会一直匹配下去直到匹配不下去为止。 (如果你发现表达式匹配的结果与预期的不符)很有可能是因为你以为表达式会只匹配前面几个字符而实际上它是greedy的因此会一直匹配下去。
Reluctant(勉强的) 用问号表示 它会匹配最少的字符。 也称为lazy,minimal matching, non-greedy,或ungreedy。
Possessive(占有的) 目前只有Java支持(其它语言都不支持)。它更加先进所以你可能还不太会用。用正则表达式匹配字符串的时候会产生很多中间状态 (一般的匹配引擎会保存这种中间状态 )这样匹配失败的时候就能原路返回了。 占有型的表达式不保存这种中间状态因此也就不会回头重来了。它能防止正则表达式的失控同时也能提高运行的效率。Greedy Reluctant Possessive 匹配
X? X?? X?+ 匹配一个或零个X
X* X*? X*+ 匹配零或多个X
X+ X+? X++ 匹配一个或多个X
X{n} X{n}? X{n}+ 匹配正好n个X
X{n,} X{n,}? X{n,}+ 匹配至少n个X
X{n,m} X{n,m}? X{n,m}+ 匹配至少n个至多m个X
再提醒一下要想让表达式照你的意思去运行你应该用括号把'X'括起来。 比方说abc+
似乎这个表达式能匹配一个或若干个'abc' 但是如果你真的用它去匹配'abcabcabc'的话实际上只会找到三个字符。因为这个表达式的意思是'ab'后边跟着一个或多个'c' 。要想匹配一个或多个完整的'abc' 你应该这样
(abc)+
正则表达式能轻而易举地把你给耍了这是一种建立在Java之上的新语言。
CharSequence
JDK 1.4定义了一个新的接口叫CharSequence。它提供了String和StringBuffer这两个类的字符序列的抽象interface CharSequence{charAt(int i);length();subSequence(int start, int end);toString();
}
为了实现这个新的CharSequence接口 String StringBuffer以及CharBuffer都作了修改。很多正则表达式的操作都要拿CharSequence作参数。
Pattern和Matcher
先给一个例子。下面这段程序可以测试正则表达式是否匹配字符串。第一个参数是要匹配的字符串后面是正则表达式。正则表达式可以有多个。在Unix/Linux环境下命令行下的正则表达式还必须用引号。
当你创建正则表达式时可以用这个程序来判断它是不是会按照你的要求工作。
//: c12:TestRegularExpression.java
//Al lows you to easly try out regular expressions.
//{Args: abcabcabcdefabc "abc+" "(abc)+" "(abc){2,}" }import java.uti l .regex.*;publ ic class TestRegularExpression {publ ic static void main(String[] args) {if(args. length <2) {
System.out.println("Usage:\n"+
"java TestRegularExpression "+
"characterSequence regularExpression+");
System.exit(0);
}
System.out.println("Input: \""+args[0]+"\"");for(int i=1; i< args. length; i++) {
System.out.println(
"Regular expression: \""+args[i]+"\"");
Pattern p=Pattern.compi le(args[i]);
Matcher m=p.matcher(args[0]);whi le(m.find()) {
System.out.println("Match \""+m.group()+
"\"at positions "+m.start()+"-"+(m.end() - 1));
}
}
}
}///:~
Java的正则表达式是由java.uti l .regex的Pattern和Matcher类实现的。 Pattern对象表示经编译的正则表达式。静态的compi le( )方法负责将表示正则表达式的字符串编译成Pattern对象。正如上述例程所示的只要给Pattern的matcher( )方法送一个字符串就能获取一个Matcher对象。此外 Pattern还有一个能快速判断能否在input里面找到regex的(注意原文有误漏了方法名)static boolean matches( regex, input)
以及能返回String数组的spl it( )方法它能用regex把字符串分割开来。
只要给Pattern.matcher( )方法传一个字符串就能获得Matcher对象了。接下来就能用Matcher的方法来查询匹配的结果了。boolean matches()boolean lookingAt()boolean find()boolean find(int start)matches( )的前提是Pattern匹配整个字符串而lookingAt( )的意思是Pattern匹配字符串的开头。find( )
Matcher.find( )的功能是发现CharSequence里的与pattern相匹配的多个字符序列。例如
//: c12:FindDemo.javaimport java.uti l .regex.*;
import com.bruceeckel .simpletest.*;import java.uti l .*;publ ic class FindDemo {private static Test monitor=new Test();publ ic static void main(String[] args) {
Matcher m=Pattern.compi le("\\w+")
.matcher("Evening is ful l of the l innet's wings");whi le(m.find())
System.out.println(m.group());int i=0;whi le(m.find(i)) {
System.out.print(m.group()+" ");i++;
}monitor.expect(new String[] {
"Evening",
"fu l l",
"l innet",
"Evening vening ening ning ing ng g is is s ful l "+
"ful l ul l l l lof of f thethe hee l innet l innet "+
"innet nnet net et ts s wings wings ings ngs gs s "
});
}
}///:~
"\\w+"的意思是"一个或多个单词字符" 因此它会将字符串直接分解成单词。 find( )像一个迭代器从头到尾扫描一遍字符串。第二个find( )是带int参数的正如你所看到的它会告诉方法从哪里开始找??即从参数位置开始查找。
Grou ps
Group是指里用括号括起来的能被后面的表达式调用的正则表达式。Group 0表示整个表达式 group 1表示第一个被括起来的group 以此类推。所以
A(B(C))D
里面有三个group group 0是ABCD group 1是BC group 2是C。
你可以用下述Matcher方法来使用group
publ ic int groupCount( )返回matcher对象中的group的数目。不包括group0。publ ic String group( )返回上次匹配操作(比方说find( ))的group 0(整个匹配)publ ic String group(int i)返回上次匹配操作的某个group。如果匹配成功但是没能找到group则返回nul l。publ ic int start(int group)返回上次匹配所找到的 group的开始位置。publ ic intend(intgroup)返回上次匹配所找到的 group的结束位置最后一个字符的下标加一。
下面我们举一些group的例子
//: c12:Groups.javaimport java.uti l .regex.*;import com.bruceeckel .simpletest.*;publ ic class Groups {private static Test monitor=new Test();static publ icfinal String poem=
"Twas bri l l ig, and the sl ithy toves\n"+
"Did gyreand gimblein thewabe.\n"+
"Al l mimsy were the borogoves,\n"+
"And the mome raths outgrabe.\n\n"+
"Beware the Jabberwock,my son,\n"+
"Thejaws that bite, the claws that catch.\n"+
"BewaretheJubjub bird, and shun\n"+
"The frumious Bandersnatch.";publ ic static void main(String[] args) {
Matcher m=
Pattern.compi le("(?m)(\\S+)\\s+((\\S+)\\s+(\\S+))$")
.matcher(poem);whi le(m.find()) {for(int j=0; j <=m.groupCount(); j++)
System.out.print("["+m.group(j)+"]");
System.out.println();
}monitor.expect(new String[]{
"[the sl ithy toves]"+
"[the][sl ithy toves][sl ithy][toves]",
"[in thewabe.][in] [the wabe. ][the][wabe. ]",
"[were the borogoves,]"+
"[were][the borogoves,][the][borogoves,]",
"[mome raths outgrabe.]"+
"[mome][raths outgrabe.][raths][outgrabe.]",
"[Jabberwock,my son,]"+
"[Jabberwock,][my son,][my][son,]",
"[claws that catch.]"+
"[claws][that catch.] [that][catch. ]",
"[bird, and shun][bird,][and shun][and][shun]",
"[The frumious Bandersnatch.][The]"+
"[frumious Bandersnatch.][frumious][Bandersnatch.]"
});
}
}///:~
这首诗是Through the Looking Glass的 Lewis Carrol l的"Jabberwocky"的第一部分。可以看到这个正则表达式里有很多用括号括起来的g rou p它是由任意多个连续的非空字符('\S+')和任意多个连续的空格字符('\s+')所组成的其最终目的是要捕获每行的最后三个单词 '$'表示一行的结尾。但是'$'通常表示整个字符串的结尾所以这里要明确地告诉正则表达式注意换行符。这一点是由'(?m)'标志完成的(模式标志会过一会讲解)。start( )和end( )
如果匹配成功 start( )会返回此次匹配的开始位置 end( )会返回此次匹配的结束位置即最后一个字符的下标加一。如果之前的匹配不成功(或者没匹配)那么无论是调用s t a rt( )还是end( )都会引发一个Il legalStateException。下面这段程序还演示了matches( )和lookingAt( )
//: c12:StartEnd.javaimport java.uti l .regex.*;import com.bruceeckel .simpletest.*;publ ic class StartEnd {private static Test monitor=new Test();publ ic static void main(String[] args) {
String[] input=new String[] {
"Java has regular expressions in 1.4",
"regular expressions now expressing in Java",
"Java represses oracular expressions"
};
Patternp1 =Pattern.compi le("re\\w*"),p2=Pattern.compi le("Java.*");for(int i=0; i< input. length; i++) {
System.out.println("input "+i+": "+input[i]);
Matcherm1 =p1.matcher(input[i]),m2=p2.matcher(input[i]);
whi le(m1.find())
System.out.println("m1.find() '"+m1.group()+
"' start="+m1.start()+"end = "+m1.end());whi le(m2.find())
System.out.println("m2.find() '"+m2.group()+
"' sta rt="+m 2.sta rt()+"en d = "+m 2.en d());if(m1. lookingAt())//No reset() necessary
System.out.println("m1. lookingAt() start="
+m1.start()+"end ="+m1.end());if(m2. lookingAt())
System.out.println("m2. lookingAt() start="
+m2.start()+"end ="+m2.end());if(m1.matches())//No reset() necessary
System.out.println("m1.matches() start="
+m1.start()+"end ="+m1.end());if(m2.matches())
System.out.println("m2.matches() start="
+m2.start()+"end ="+m2.end());
}monitor.expect(new String[] {
"input 0: Java has regularexpressions in 1.4","m1.find() 'regular' start=9 end =16",
"m1.find() 'ressions' start=20 end =28",
"m2.find() 'Java has regular expressions in 1.4'"+" start=0 end =35",
"m2. lookingAt() start=0 end =35",
"m2.matches() start=0 end =35",
"input 1: regular expressions now"+
"expressing in Java",
"m1.find() 'regular' start=0 end =7",
"m1.find() 'ressions' start= 11 end =19",
"m1.find() 'ressing' start=27 end =34",
"m2.find() 'Java' start=38 end =42",
"m1. lookingAt() start=0 end =7",
"input 2: Java represses oracular expressions","m1.find() 'represses' start=5 end = 14",
"m1.find() 'ressions' start=27 end =35",
"m2.find() 'Java represses oracular expressions' "+"start=0 end =35",
"m2. lookingAt() start=0 end =35",
"m2.matches() start=0 end =35"
});
}
}///:~
注意 只要字符串里有这个模式 find( )就能把它给找出来但是lookingAt( )和matches( ) 只有在字符串与正则表达式一开始就相匹配的情况下才能返回true。matches( )成功的前提是正则表达式与字符串完全匹配而lookingAt( )[67]成功的前提是字符串的开始部分与正则表达式相匹配。
匹配的模式(Pattern flags)compi le( )方法还有一个版本它需要一个控制正则表达式的匹配行为的参数
Pattern Pattern.compi le(String regex, int flag)flag的取值范围如下 编译标志效果
Pattern.CANON_EQ 当且仅当两个字符的"正规分解(canonical decomposition)"都完全相同的情况下才认定匹配。 比如用了这个标志之后表达式"a\u030A"会匹配"?"。默认情况下不考虑"规范相等性(canonical equivalence)"。
Pattern.CASE_INSENSITIVE
(?i)默认情况下大小写不明感的匹配只适用于US-ASCII字符集。这个标志能让表达式忽略大小写进行匹配。 要想对Unicode字符进行大小不明感的匹配 只要将UNICODE_CASE与这个标志合起来就行了。
Pattern.COMMENTS
(?x)在这种模式下匹配时会忽略(正则表达式里的)空格字符(译者注不是指表达式里的"\\s"而是指表达式里的空格 tab 回车之类)。注释从#开始一直到这行结束。可以通过嵌入式的标志来启用Unix行模式。
Pattern.DOTALL
(?s)在这种模式下表达式' . '可以匹配任意字符包括表示一行的结束符。默认情况下表达式' . '不匹配行的结束符。
Pattern.MULTILINE
(?m)在这种模式下 '^'和'$'分别匹配一行的开始和结束。此外 '^'仍然匹配字符串的开始 '$'也匹配字符串的结束。默认情况下这两个表达式仅仅匹配字符串的开始和结束。Pattern.UNICODE_CASE
(?u)在这个模式下如果你还启用了CASE_INSENSITIVE标志那么它会对Unicode字符进行大小写不明感的匹配。默认情况下大小写不明感的匹配只适用于US-ASCII字符集。
Pattern.UNIX_LINES
(?d)在这个模式下只有'\n'才被认作一行的中止并且与' . ' '^' 以及'$'进行匹配。在这些标志里面 Pattern.CASE_INSENSITIVE Pattern.MULTILINE 以及Pattern.COMMENTS是最有用的(其中Pattern.COMMENTS还能帮我们把思路理清楚并且/或者做文档)。注意你可以用在表达式里插记号的方式来启用绝大多数的模式。这些记号就在上面那张表的各个标志的下面。你希望模式从哪里开始启动就在哪里插记号。可以用"OR" (' | ')运算符把这些标志合使用
//: c12:ReFlags.java