java正規表示式例項

okone96發表於2007-05-16

Java正規表示式例項詳解 [轉]
建立正規表示式
你可以從比較簡單的東西入手學習正規表示式。要想全面地掌握怎樣構建正規表示式,可以去看JDK文件的java.util.regex的Pattern類的文件。 字元
B 字元B
xhh 16進位制值0xhh所表示的字元
uhhhh 16進位制值0xhhhh所表示的Unicode字元
t Tab
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裡,""的意思是"我要插入一個正規表示式的反斜槓,所以跟在它後面的那個字元的意思就變了。"舉例來說,如果你想表示一個或更多的"單詞字元",那麼這個正規表示式就應該是"+"。如果你要插入一個反斜槓,那就得用""。不過像換行,跳格之類的還是隻用一根反斜槓:"nt"。

這裡只給你講一個例子;你應該JDK文件的java.util.regex.Pattern加到收藏夾裡,這樣就能很容易地找到各種正規表示式的模式了。 邏輯運算子
XY X 後面跟著 Y
X|Y X或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
// Allows you to easly try out regular expressions.
// {Args: abcabcabcdefabc "abc+" "(abc)+" "(abc){2,}" }
import java.util.regex.*;
public class TestRegularExpression {
public 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.compile(args[i]);
Matcher m = p.matcher(args[0]);
while(m.find()) {
System.out.println("Match "" + m.group() +
"" at positions " +
m.start() + "-" + (m.end() - 1));
}
}
}
} ///:~


Java的正規表示式是由java.util.regex的Pattern和Matcher類實現的。Pattern物件表示經編譯的正規表示式。靜態的compile( )方法負責將表示正規表示式的字串編譯成Pattern物件。正如上述例程所示的,只要給Pattern的matcher( )方法送一個字串就能獲取一個Matcher物件。此外,Pattern還有一個能快速判斷能否在input裡面找到regex的(注意,原文有誤,漏了方法名)

static boolean matches( regex, input)以及能返回String陣列的split( )方法,它能用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.java
import java.util.regex.*;
import com.bruceeckel.simpletest.*;
import java.util.*;
public class FindDemo {
private static Test monitor = new Test();
public static void main(String[] args) {
Matcher m = Pattern.compile("+")
.matcher("Evening is full of the linnet's wings");
while(m.find())
System.out.println(m.group());
int i = 0;
while(m.find(i)) {
System.out.print(m.group() + " ");
i++;
}
monitor.expect(new String[] {
"Evening",
"is",
"full",
"of",
"the",
"linnet",
"s",
"wings",
"Evening vening ening ning ing ng g is is s full " +
"full ull ll l of of f the the he e linnet linnet " +
"innet nnet net et t s s wings wings ings ngs gs s "
});
}
} ///:~


"+"的意思是"一個或多個單詞字元",因此它會將字串直接分解成單詞。find( )像一個迭代器,從頭到尾掃描一遍字串。第二個find( )是帶int引數的,正如你所看到的,它會告訴方法從哪裡開始找——即從引數位置開始查詢。

Groups
Group是指裡用括號括起來的,能被後面的表示式呼叫的正規表示式。Group 0 表示整個表示式,group 1表示第一個被括起來的group,以此類推。所以;

A(B(C))D裡面有三個group:group 0是ABCD, group 1是BC,group 2是C。

你可以用下述Matcher方法來使用group:

public int groupCount( )返回matcher物件中的group的數目。不包括group0。

public String group( ) 返回上次匹配操作(比方說find( ))的group 0(整個匹配)

public String group(int i)返回上次匹配操作的某個group。如果匹配成功,但是沒能找到group,則返回null。

public int start(int group)返回上次匹配所找到的,group的開始位置。

public int end(int group)返回上次匹配所找到的,group的結束位置,最後一個字元的下標加一。

下面我們舉一些group的例子: //: c12:Groups.java
import java.util.regex.*;
import com.bruceeckel.simpletest.*;
public class Groups {
private static Test monitor = new Test();
static public final String poem =
"Twas brillig, and the slithy tovesn" +
"Did gyre and gimble in the wabe.n" +
"All mimsy were the borogoves,n" +
"And the mome raths outgrabe.nn" +
"Beware the Jabberwock, my son,n" +
"The jaws that bite, the claws that catch.n" +
"Beware the Jubjub bird, and shunn" +
"The frumious Bandersnatch.";
public static void main(String[] args) {
Matcher m =
Pattern.compile("(?m)(")
.matcher(poem);
while(m.find()) {
for(int j = 0; j <= m.groupCount(); j++)
System.out.print("[" + m.group(j) + "]");
System.out.println();
}
monitor.expect(new String[]{
"[the slithy toves]" +
"[the][slithy toves][slithy][toves]",
"[in the wabe.][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 Carroll的"Jabberwocky"的第一部分。可以看到這個正規表示式裡有很多用括號括起來的group,它是由任意多個連續的非空字元('S+')和任意多個連續的空格字元('s+')所組成的,其最終目的是要捕獲每行的最後三個單詞;'$'表示一行的結尾。但是'$'通常表示整個字串的結尾,所以這裡要明確地告訴正規表示式注意換行符。這一點是由'(?m)'標誌完成的(模式標誌會過一會講解)。

start( )和end( )
如果匹配成功,start( )會返回此次匹配的開始位置,end( )會返回此次匹配的結束位置,即最後一個字元的下標加一。如果之前的匹配不成功(或者沒匹配),那麼無論是呼叫start( )還是end( ),都會引發一個IllegalStateException。下面這段程式還演示了matches( )和lookingAt( ): //: c12:StartEnd.java
import java.util.regex.*;
import com.bruceeckel.simpletest.*;
public class StartEnd {
private static Test monitor = new Test();
public 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"
};
Pattern
p1 = Pattern.compile("rew*"),
p2 = Pattern.compile("Java.*");
for(int i = 0; i < input.length; i++) {
System.out.println("input " + i + ": " + input[i]);
Matcher
m1 = p1.matcher(input[i]),
m2 = p2.matcher(input[i]);
while(m1.find())
System.out.println("m1.find() '" + m1.group() +
"' start = "+ m1.start() + " end = " + m1.end());
while(m2.find())
System.out.println("m2.find() '" + m2.group() +
"' start = "+ m2.start() + " end = " + m2.end());
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 regular expressions 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)
compile( )方法還有一個版本,它需要一個控制正規表示式的匹配行為的引數:

Pattern Pattern.compile(String regex, int flag)flag的取值範圍如下: 編譯標誌 效果
Pattern.CANON_EQ 當且僅當兩個字元的"正規分解(canonical decomposition)"都完全相同的情況下,才認定匹配。比如用了這個標誌之後,表示式"au030A"會匹配"?"。預設情況下,不考慮"規範相等性(canonical equivalence)"。
Pattern.CASE_INSENSITIVE
(?i) 預設情況下,大小寫不明感的匹配只適用於US-ASCII字符集。這個標誌能讓表示式忽略大小寫進行匹配。要想對Unicode字元進行大小不明感的匹配,只要將UNICODE_CASE與這個標誌合起來就行了。
Pattern.COMMENTS
(?x) 在這種模式下,匹配時會忽略(正規表示式裡的)空格字元(譯者注:不是指表示式裡的"",而是指表示式裡的空格,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
import 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 regexnJava has regexn" +
"JAVA has pretty good regular expressionsn" +
"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.java
import 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.java
import 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.java
import 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 "+"}
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.java
import 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)。這本書講得更深。

[@more@]

來自 “ ITPUB部落格 ” ,連結:http://blog.itpub.net/750220/viewspace-915220/,如需轉載,請註明出處,否則將追究法律責任。

相關文章