你能夠從比較簡單的東西入手學習正則表達式。要想全面地掌握怎樣構建正則表達式,能夠去看JDK 文檔的java.util.regex 的Pattern 類的文檔。java
字符 | |
---|---|
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裏,"\\ "的意思是"我要插入一個正則表達式的反斜槓,因此跟在它後面的那個字符的意思就變了。"舉例來講,若是你想表示一個或更多的"單詞字符",那麼這個正則表達式就應該是"\\w+ "。若是你要插入一個反斜槓,那就得用"\\\\"。不過像換行,跳格之類的仍是隻用一根反斜槓:"\n\t"。express
這裏只給你講一個例子;你應該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)"的做用是定義模式應該匹配多少個字符。app
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 之上的新語言。測試
JDK 1.4定義了一個新的接口,叫CharSequence 。它提供了String 和StringBuffer 這兩個類的字符序列的抽象:ui
interface CharSequence { charAt(int i); length(); subSequence(int start, int end); toString(); }
爲了實現這個新的CharSequence 接口,String ,StringBuffer 以及CharBuffer 都做了修改。不少正則表達式的操做都要拿CharSequence 做參數。
先給一個例子。下面這段程序能夠測試正則表達式是否匹配字符串。第一個參數是要匹配的字符串,後面是正則表達式。正則表達式能夠有多個。在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 匹配字符串的開頭。
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("\\w+" ) .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 " }); } } ///:~ |
"\\w+ "的意思是"一個或多個單詞字符",所以它會將字符串直接分解成單詞。find( ) 像一個迭代器,從頭至尾掃描一遍字符串。第二個find( ) 是帶int 參數的,正如你所看到的,它會告訴方法從哪裏開始找——即從參數位置開始查找。
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 toves\n" + "Did gyre and gimble in the wabe.\n" + "All mimsy were the borogoves,\n" + "And the mome raths outgrabe.\n\n" + "Beware the Jabberwock, my son,\n" + "The jaws that bite, the claws that catch.\n" + "Beware the Jubjub bird, and shun\n" + "The frumious Bandersnatch." ; public static void main(String[] args) { Matcher m = Pattern.compile("(?m)(\\S+)\\s+((\\S+)\\s+(\\S+))$" ) .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( ) ,都會引起一個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("re\\w*" ), 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] 成功的前提是,字符串的開始部分與正則表達式相匹配。
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.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 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( ) 方法僅返回匹配的部分。
所謂分割是指將以正則表達式爲界,將字符串分割成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( ) 方法給現有的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 設到當前字符串的開始處。
到目前爲止,你看到的都是用正則表達式處理靜態字符串的例子。下面咱們來演示一下怎樣用正則表達式掃描文件而且找出匹配的字符串。受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 。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)。這本書講得更深。