vmware nat模式配置:Java正则表达式实例详解

来源:百度文库 编辑:中财网 时间:2024/04/30 00:17:33

匹配的模式(Pattern flags)

compile( )方法还有一个版本,它需要一个控制正则表达式的匹配行为的参数:

Pattern Pattern.compile(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.javaimport java.util.regex.*;import com.bruceeckel.simpletest.*;public class ReFlags {  private static Test monitor = new Test();  public static void main(String[] args) {    Pattern p =  Pattern.compile("^java",      Pattern.CASE_INSENSITIVE | Pattern.MULTILINE);    Matcher m = p.matcher(      "java has regex\nJava has regex\n" +      "JAVA has pretty good regular expressions\n" +      "Regular expressions are in Java");    while(m.find())      System.out.println(m.group());    monitor.expect(new String[] {      "java",      "Java",      "JAVA"    });  }} ///:~

这样创建出来的正则表达式就能匹配以"java","Java","JAVA"...开头的字符串了。此外,如果字符串分好几行,那它还会对每一行做匹配(匹配始于字符序列的开始,终于字符序列当中的行结束符)。注意,group( )方法仅返回匹配的部分。

split( )

所谓分割是指将以正则表达式为界,将字符串分割成String数组。

String[] split(CharSequence charseq)String[] split(CharSequence charseq, int limit)

这是一种既快又方便地将文本根据一些常见的边界标志分割开来的方法。

//: c12:SplitDemo.javaimport java.util.regex.*;import com.bruceeckel.simpletest.*;import java.util.*;public class SplitDemo {  private static Test monitor = new Test();  public static void main(String[] args) {    String input =      "This!!unusual use!!of exclamation!!points";    System.out.println(Arrays.asList(      Pattern.compile("!!").split(input)));    // Only do the first three:    System.out.println(Arrays.asList(      Pattern.compile("!!").split(input, 3)));    System.out.println(Arrays.asList(      "Aha! String has a split() built in!".split(" ")));    monitor.expect(new String[] {      "[This, unusual use, of exclamation, points]",      "[This, unusual use, of exclamation!!points]",      "[Aha!, String, has, a, split(), built, in!]"    });  }} ///:~

第二个split( )会限定分割的次数。

正则表达式是如此重要,以至于有些功能被加进了String类,其中包括split( )(已经看到了),matches( ),replaceFirst( )以及replaceAll( )。这些方法的功能同Pattern和Matcher的相同。

替换操作

正则表达式在替换文本方面特别在行。下面就是一些方法:

replaceFirst(String replacement)将字符串里,第一个与模式相匹配的子串替换成replacement。

replaceAll(String replacement),将输入字符串里所有与模式相匹配的子串全部替换成replacement。

appendReplacement(StringBuffer sbuf, String replacement)对sbuf进行逐次替换,而不是像replaceFirst( )或replaceAll( )那样,只替换第一个或全部子串。这是个非常重要的方法,因为它可以调用方法来生成replacement(replaceFirst( )和replaceAll( )只允许用固定的字符串来充当replacement)。有了这个方法,你就可以编程区分group,从而实现更强大的替换功能。

调用完appendReplacement( )之后,为了把剩余的字符串拷贝回去,必须调用appendTail(StringBuffer sbuf, String replacement)。

下面我们来演示一下怎样使用这些替换方法。说明一下,这段程序所处理的字符串是它自己开头部分的注释,是用正则表达式提取出来并加以处理之后再传给替换方法的。

//: c12:TheReplacements.javaimport java.util.regex.*;import java.io.*;import com.bruceeckel.util.*;import com.bruceeckel.simpletest.*;/*! Here's a block of text to use as input to    the regular expression matcher. Note that we'll    first extract the block of text by looking for    the special delimiters, then process the    extracted block. !*/public class TheReplacements {  private static Test monitor = new Test();  public static void main(String[] args) throws Exception {    String s = TextFile.read("TheReplacements.java");    // Match the specially-commented block of text above:    Matcher mInput =      Pattern.compile("/\\*!(.*)!\\*/", Pattern.DOTALL)        .matcher(s);    if(mInput.find())      s = mInput.group(1); // Captured by parentheses    // Replace two or more spaces with a single space:    s = s.replaceAll(" {2,}", " ");    // Replace one or more spaces at the beginning of each    // line with no spaces. Must enable MULTILINE mode:    s = s.replaceAll("(?m)^ +", "");    System.out.println(s);    s = s.replaceFirst("[aeiou]", "(VOWEL1)");    StringBuffer sbuf = new StringBuffer();    Pattern p = Pattern.compile("[aeiou]");    Matcher m = p.matcher(s);    // Process the find information as you    // perform the replacements:    while(m.find())      m.appendReplacement(sbuf, m.group().toUpperCase());    // Put in the remainder of the text:    m.appendTail(sbuf);    System.out.println(sbuf);    monitor.expect(new String[]{      "Here's a block of text to use as input to",      "the regular expression matcher. Note that we'll",      "first extract the block of text by looking for",      "the special delimiters, then process the",      "extracted block. ",      "H(VOWEL1)rE's A blOck Of tExt tO UsE As InpUt tO",      "thE rEgUlAr ExprEssIOn mAtchEr. NOtE thAt wE'll",      "fIrst ExtrAct thE blOck Of tExt by lOOkIng fOr",      "thE spEcIAl dElImItErs, thEn prOcEss thE",      "ExtrActEd blOck. "    });  }} ///:~

我们用前面介绍的TextFile.read( )方法来打开和读取文件。mInput的功能是匹配'/*!' 和 '!*/' 之间的文本(注意一下分组用的括号)。接下来,我们将所有两个以上的连续空格全都替换成一个,并且将各行开头的空格全都去掉(为了让这个正则表达式能对所有的行,而不仅仅是第一行起作用,必须启用多行模式)。这两个操作都用了String的replaceAll( )(这里用它更方便)。注意,由于每个替换只做一次,因此除了预编译Pattern之外,程序没有额外的开销。

replaceFirst( )只替换第一个子串。此外,replaceFirst( )和replaceAll( )只能用常量(literal)来替换,所以如果你每次替换的时候还要进行一些操作的话,它们是无能为力的。碰到这种情况,你得用appendReplacement( ),它能让你在进行替换的时候想写多少代码就写多少。在上面那段程序里,创建sbuf的过程就是选group做处理,也就是用正则表达式把元音字母找出来,然后换成大写的过程。通常你得在完成全部的替换之后才调用appendTail( ),但是如果要模仿replaceFirst( )(或"replace n")的效果,你也可以只替换一次就调用appendTail( )。它会把剩下的东西全都放进sbuf。

你还可以在appendReplacement( )的replacement参数里用"$g"引用已捕获的group,其中'g' 表示group的号码。不过这是为一些比较简单的操作准备的,因而其效果无法与上述程序相比。

reset( )

此外,还可以用reset( )方法给现有的Matcher对象配上个新的CharSequence。

//: c12:Resetting.javaimport java.util.regex.*;import java.io.*;import com.bruceeckel.simpletest.*;public class Resetting {  private static Test monitor = new Test();  public static void main(String[] args) throws Exception {    Matcher m = Pattern.compile("[frb][aiu][gx]")      .matcher("fix the rug with bags");    while(m.find())      System.out.println(m.group());    m.reset("fix the rig with rags");    while(m.find())      System.out.println(m.group());    monitor.expect(new String[]{      "fix",      "rug",      "bag",      "fix",      "rig",      "rag"    });  }} ///:~

如果不给参数,reset( )会把Matcher设到当前字符串的开始处。

正则表达式与Java I/O

到目前为止,你看到的都是用正则表达式处理静态字符串的例子。下面我们来演示一下怎样用正则表达式扫描文件并且找出匹配的字符串。受Unix的grep启发,我写了个JGrep.java,它需要两个参数:文件名,以及匹配字符串用的正则表达式。它会把匹配这个正则表达式那部分内容及其所属行的行号打印出来。

//: c12:JGrep.java// A very simple version of the "grep" program.// {Args: JGrep.java "\\b[Ssct]\\w+"}import java.io.*;import java.util.regex.*;import java.util.*;import com.bruceeckel.util.*;public class JGrep {  public static void main(String[] args) throws Exception {    if(args.length < 2) {      System.out.println("Usage: java JGrep file regex");      System.exit(0);    }    Pattern p = Pattern.compile(args[1]);    // Iterate through the lines of the input file:    ListIterator it = new TextFile(args[0]).listIterator();    while(it.hasNext()) {      Matcher m = p.matcher((String)it.next());      while(m.find())        System.out.println(it.nextIndex() + ": " +          m.group() + ": " + m.start());    }  }} ///:~

文件是用TextFile打开的(本章的前半部分讲的)。由于TextFile会把文件的各行放在ArrayList里面,而我们又提取了一个ListIterator,因此我们可以在文件的各行当中自由移动(既能向前也可以向后)。

每行都会有一个Matcher,然后用find( )扫描。注意,我们用ListIterator.nextIndex( )跟踪行号。

测试参数是JGrep.java和以[Ssct]开头的单词。

还需要StringTokenizer吗?

看到正则表达式能提供这么强大的功能,你可能会怀疑,是不是还需要原先的StringTokenizer。JDK 1.4以前,要想分割字符串,只有用StringTokenizer。但现在,有了正则表达式之后,它就能做得更干净利索了。

//: c12:ReplacingStringTokenizer.javaimport java.util.regex.*;import com.bruceeckel.simpletest.*;import java.util.*;public class ReplacingStringTokenizer {  private static Test monitor = new Test();  public static void main(String[] args) {    String input = "But I'm not dead yet! I feel happy!";    StringTokenizer stoke = new StringTokenizer(input);    while(stoke.hasMoreElements())      System.out.println(stoke.nextToken());    System.out.println(Arrays.asList(input.split(" ")));    monitor.expect(new String[] {      "But",      "I'm",      "not",      "dead",      "yet!",      "I",      "feel",      "happy!",      "[But, I'm, not, dead, yet!, I, feel, happy!]"    });  }} ///:~

有了正则表达式,你就能用更复杂的模式将字符串分割开来——要是交给StringTokenizer的话,事情会麻烦得多。我可以很有把握地说,正则表达式可以取代StringTokenizer。

要想进一步学习正则表达式,建议你看Mastering Regular Expression, 2nd Edition,作者Jeffrey E. F. Friedl (O'Reilly, 2002)。

总结

Java的I/O流类库应该能满足你的基本需求:你可以用它来读写控制台,文件,内存,甚至是Internet。你还可以利用继承来创建新的输入和输出类型。你甚至可以利用Java会自动调用对象的toString( )方法的特点(Java仅有的"自动类型转换"),通过重新定义这个方法,来对要传给流的对象做一个简单的扩展。

但是Java的I/O流类库及其文档还是留下了一些缺憾。比方说你打开一个文件往里面写东西,但是这个文件已经有了,这么做会把原先的内容给覆盖了 。这时要是能有一个异常就好了——有些编程语言能让你规定只能往新建的文件里输出。看来Java是要你用File对象来判断文件是否存在,因为如果你用FileOutputStream或FileWriter的话,文件就会被覆盖了。

我对I/O流类库的评价是比较矛盾的;它确实能干很多事情,而且做到了跨平台。但是如果你不懂decorator模式,就会觉得这种设计太难理解了,所以无论是对老师还是学生,都得多花精力。此外这个类库也不完整,否则我也用不着去写TextFile了。此外它没有提供格式化输出的功能,而其他语言都已经提供了这种功能。

但是,一旦你真正理解了decorator模式,并且能开始灵活运用这个类库的时候,你就能感受到这种设计的好处了。这时多写几行代码就算不了什么了。

如果你觉得不解渴(本章只是做个介绍,没想要面面俱到),可以去看Elliotte Rusty Harold 写的Java I/O (O'Reilly, 1999)。这本书讲得更深。

import java.util.regex.*;

import java.util.*;

public class Test {
private static Test monitor = new Test();

public static void main(String[] args) {
   String input = "This!!unusual use!!of exclamation!!points";
   System.out.println(Arrays.asList(Pattern.compile("!!").split(input)));
   // Only do the first three:    System.out.println(Arrays.asList(    
// Pattern.compile("!!").split(input, 3)));   
   System.out.println(Arrays.asList(      "Aha! String has a split() built in!".split(" ")));    
     System.out.println(Arrays.asList(      Pattern.compile("!!").split(input, 10)));
//   monitor.expect(new String[] {    
//     "[This, unusual use, of exclamation, points]",    
//     "[This, unusual use, of exclamation!!points]",   
//     "[Aha!, String, has, a, split(), built, in!]" 
//     }); }} ///:~
   // List a = new List();
     
     HashMap     hashmap     =     new     HashMap();   
         hashmap.put("Item0",     "Value0");   
         hashmap.put("Item1",     "Value1");   
         hashmap.put("Item2",     "Value2");   
         hashmap.put("Item3",     "Value3");   
         Set     set     =     hashmap.entrySet();   
         Iterator     iterator     =     set.iterator();   
         while     (iterator.hasNext() ) 
       {   
           Map.Entry     mapentry     =     (Map.Entry)     iterator.next();   
           System.out.println(mapentry.getKey() +     "/"     +     mapentry.getValue());   
       }  

}
}