在Sun的Java JDK 1.40版本中,Java自帶了支持正則表達式的包,本文就拋磚引玉地介紹瞭如何使用java.util.regex包。
可粗略估計一下,除了偶爾用Linux的外,其他Linu x用戶都會遇到正則表達式。正則表達式是個極端強大工具,而且在字符串模式-匹配和字符串模式-替換方面富有彈性。在Unix世界裏,正則表達式幾乎沒有什麼限制,可肯定的是,它應用非常之廣泛。
正則表達式的引擎已被許多普通的Unix工具所實現,包括grep,awk,vi和Emacs等。此外,許多使用比較廣泛的腳本語言也支持正則表達式,比如Python,Tcl,JavaScript,以及最著名的Perl。
我很早以前就是個Perl方面的黑客,如果你和我一樣話,你也會非常依賴你手邊的這些強大的text-munging工具。近幾年來,像其他程序開發者一樣,我也越來越關注Java的開發。
Java作爲一種開發語言,有許多值得推薦的地方,但是它一直以來沒有自帶對正則表達式的支持。直到最近,藉助於第三方的類庫,Java開始支持正則表達式,但這些第三方的類庫都不一致、兼容性差,而且維護代碼起來很糟糕。這個缺點,對我選擇Java作爲首要的開發工具來說,一直是個巨大的顧慮之處。
你可以想象,當我知道Sun的Java JDK 1.40版本包含了java.util.regex(一個完全開放、自帶的正則表達式包)時,是多麼的高興!很搞笑的說,我花好些時間去挖掘這個被隱藏起來的寶石。我非常驚奇的是,Java這樣的一個很大改進(自帶了java.util.regex包)爲什麼不多公開一點呢?!
最近,Java雙腳都跳進了正則表達式的世界。java.util.regex包在支持正則表達也有它的過人之處,另外Java也提供詳細的相關說明文檔。使得朦朦朧朧的regex神祕景象也慢慢被撥開。有一些正則表達式的構成(可能最顯著的是,在於糅合了字符類庫)在Perl都找不到。
在regex包中,包括了兩個類,Pattern(模式類)和Matcher(匹配器類)。Pattern類是用來表達和陳述所要搜索模式的對象,Matcher類是真正影響搜索的對象。另加一個新的例外類,PatternSyntaxException,當遇到不合法的搜索模式時,會拋出例外。
即使對正則表達式很熟悉,你會發現,通過java使用正則表達式也相當簡單。要說明的一點是,對那些被Perl的單行匹配所寵壞的Perl狂熱愛好者來說,在使用java的regex包進行替換操作時,會比他們所以前常用的方法費事些。
本文的侷限之處,它不是一篇正則表達式用法的完全教程。如果讀者要對正則表達進一步瞭解的話,推薦閱讀Jeffrey Frieldl的Mastering Regular Expressions,該書由O’Reilly出版社出版。我下面就舉一些例子來教讀者如何使用正則表達式,以及如何更簡單地去使用它。
設計一個簡單的表達式來匹配任何電話號碼數字可能是比較複雜的事情,原因在於電話號碼格式有很多種情況。所有必須選擇一個比較有效的模式。比如:(212) 555-1212, 212-555-1212和212 555 1212,某些人會認爲它們都是等價的。
首先讓我們構成一個正則表達式。爲簡單起見,先構成一個正則表達式來識別下面格式的電話號碼數字:(nnn)nnn-nnnn。
第一步,創建一個pattern對象來匹配上面的子字符串。一旦程序運行後,如果需要的話,可以讓這個對象一般化。匹配上面格式的正則表達可以這樣構成:(/d{3})/s/d{3}-/d{4},其中/d單字符類型用來匹配從0到9的任何數字,另外{3}重複符號,是個簡便的記號,用來表示有3個連續的數字位,也等效於(/d/d/d)。/s也另外一個比較有用的單字符類型,用來匹配空格,比如Space鍵,tab鍵和換行符。
是不是很簡單?但是,如果把這個正則表達式的模式用在java程序中,還要做兩件事。對java的解釋器來說,在反斜線字符(/)前的字符有特殊的含義。在java中,與regex有關的包,並不都能理解和識別反斜線字符(/),儘管可以試試看。但爲避免這一點,即爲了讓反斜線字符(/)在模式對象中被完全地傳遞,應該用雙反斜線字符(/)。此外圓括號在正則表達中兩層含義,如果想讓它解釋爲字面上意思(即圓括號),也需要在它前面用雙反斜線字符(/)。也就是像下面的一樣:
//(//d{3}//)//s//d{3}-//d{4}
現在介紹怎樣在java代碼中實現剛纔所講的正則表達式。要記住的事,在用正則表達式的包時,在你所定義的類前需要包含該包,也就是這樣的一行:
import java.util.regex.*;
下面的一段代碼實現的功能是,從一個文本文件逐行讀入,並逐行搜索電話號碼數字,一旦找到所匹配的,然後輸出在控制檯。
BufferedReader in;
Pattern pattern = Pattern.compile("//(//d{3}//)//s//d{3}-//d{4}");
in = new BufferedReader(new FileReader("phone"));
String s;
while ((s = in.readLine()) != null)
{
Matcher matcher = pattern.matcher(s);
if (matcher.find())
{
System.out.println(matcher.group());
}
}
in.close();
對那些熟悉用Python或Javascript來實現正則表達式的人來說,這段代碼很平常。在Python和Javascript這些語言中,或者其他的語言,這些正則表達式一旦明確地編譯過後,你想用到哪裏都可以。與Perl的單步匹配相比,看起來多多做了些工作,但這並不很費事。
find()方法,就像你所想象的,用來搜索與正則表達式相匹配的任何目標字符串,group()方法,用來返回包含了所匹配文本的字符串。應注意的是,上面的代碼,僅用在每行只能含有一個匹配的電話號碼數字字符串時。可以肯定的說,java的正則表達式包能用在一行含有多個匹配目標時的搜索。本文的原意在於舉一些簡單的例子來激起讀者進一步去學習java自帶的正則表達式包,所以對此就沒有進行深入的探討。
這相當漂亮吧! 但是很遺憾的是,這僅是個電話號碼匹配器。很明顯,還有兩點可以改進。如果在電話號碼的開頭,即區位號和本地號碼之間可能會有空格。我們也可匹配這些情況,則通過在正則表達式中加入/s?來實現,其中?元字符表示在模式可能有0或1個空格符。
第二點是,在本地號碼位的前三位和後四位數字間有可能是空格符,而不是連字號,更有勝者,或根本就沒有分隔符,就是7位數字連在一起。對這幾種情況,我們可以用(-|)?來解決。這個結構的正則表達式就是轉換器,它能匹配上面所說的幾種情況。在()能含有管道符|時,它能匹配是否含有空格符或連字符,而尾部的?元字符表示是否根本沒有分隔符的情況。
最後,區位號也可能沒有包含在圓括號內,對此可以簡單地在圓括號後附上?元字符,但這不是一個很好的解決方法。因爲它也包含了不配對的圓括號,比如"(555" 或 "555)"。相反,我們可以通過另一種轉換器來強迫讓電話號碼是否帶有有圓括號:(/(/d{3}/)|/d{3})。如果我們把上面代碼中的正則表達式用這些改進後的來替換的話,上面的代碼就成了一個非常有用的電話號碼數字匹配器:
Pattern pattern =
Pattern.compile("(//(//d{3}//)|//d{3})//s?//d{3}(-|)?//d{4}");
可以確定的是,你可以自己試着進一步改進上面的代碼。
現在看看第二個例子,它是從Friedl的中改編過來的。其功能是用來檢查文本文件中是否有重複的單詞,這在印刷排版中會經常遇到,同樣也是個語法檢查器的問題。
匹配單詞,像其他的一樣,也可以通過好幾種的正則表達式來完成。可能最直接的是/b/w+/b,其優點在於只需用少量的regex元字符。其中/w元字符用來匹配從字母a到u的任何字符。+元字符表示匹配匹配一次或多次字符,/b元字符是用來說明匹配單詞的邊界,它可以是空格或任何一種不同的標點符號(包括逗號,句號等)。
現在,我們怎樣來檢查一個給定的單詞是否被重複了三次?爲完成這個任務,需充分利用正則表達式中的所熟知的向後掃描。如前面提到的,圓括號在正則表達式中有幾種不同的用法,一個就是能提供組合類型,組合類型用來保存所匹配的結果或部分匹配的結果(以便後面能用到),即使遇到有相同的模式。在同樣的正則表達中,可能(也通常期望)不止有一個組合類型。在第n個組合類型中匹配結果可以通過向後掃描來獲取到。向後掃描使得搜索重複的單詞非常簡單:/b(/w+)/s+/1/b。
圓括號形成了一個組合類型,在這個正則表示中它是第一組合類型(也是僅有的一個)。向後掃描/1,指的是任何被/w+所匹配的單詞。我們的正則表達式因此能匹配這樣的單詞,它有一個或多個空格符,後面還跟有一個與此相同的單詞。注意的是,尾部的定位類型(/b)必不可少,它可以防止發生錯誤。如果我們想匹配"Paris in the the spring",而不是匹配"Java's regex package is the theme of this article"。根據java現在的格式,則上面的正則表達式就是:Pattern pattern =Pattern.compile("//b(//w+)//s+//1//b");
最後進一步的修改是讓我們的匹配器對大小寫敏感。比如,下面的情況:"The the theme of this article is the Java's regex package.",這一點在regex中能非常簡單地實現,即通過使用在Pattern類中預定義的靜態標誌CASE_INSENSITIVE :
Pattern pattern =Pattern.compile("//b(//w+)//s+//1//b",
Pattern.CASE_INSENSITIVE);
有關正則表達式的話題是非常豐富,而且複雜的,用Java來實現也非常廣泛,則需要對regex包進行的徹底研究,我們在這裏所講的只是冰山一角。即使你對正則表達式比較陌生,使用regex包後會很快發現它強大功能和可伸縮性。如果你是個來自Perl或其他語言王國的老練的正則表達式的黑客,使用過regex包後,你將會安心地投入到java的世界,而放棄其他的工具,並把java的regex包看成是手邊必備的利器。
如果你曾經用過Perl或任何其他內建正則表達式支持的語言,你一定知道用正則表達式處理文本和匹配模式是多麼簡單。如果你不熟悉這個術語,那麼“正則表達式”(Regular Expression)就是一個字符構成的串,它定義了一個用來搜索匹配字符串的模式。 |
許多語言,包括Perl、PHP、Python、JavaScript和JScript,都支持用正則表達式處理文本,一些文本編輯器用正則表達式實現高級“搜索-替換”功能。那麼Java又怎樣呢?本文寫作時,一個包含了用正則表達式進行文本處理的Java規範需求(Specification Request)已經得到認可,你可以期待在JDK的下一版本中看到它。 |
然而,如果現在就需要使用正則表達式,又該怎麼辦呢?你可以從Apache.org下載源代碼開放的Jakarta-ORO庫。本文接下來的內容先簡要地介紹正則表達式的入門知識,然後以Jakarta-ORO API爲例介紹如何使用正則表達式。 |
一、正則表達式基礎知識 |
我們先從簡單的開始。假設你要搜索一個包含字符“cat”的字符串,搜索用的正則表達式就是“cat”。如果搜索對大小寫不敏感,單詞“catalog”、“Catherine”、“sophisticated”都可以匹配。也就是說: |
1.1 句點符號 |
假設你在玩英文拼字遊戲,想要找出三個字母的單詞,而且這些單詞必須以“t”字母開頭,以“n”字母結束。另外,假設有一本英文字典,你可以用正則表達式搜索它的全部內容。要構造出這個正則表達式,你可以使用一個通配符——句點符號“.”。這樣,完整的表達式就是“t.n”,它匹配“tan”、“ten”、“tin”和“ton”,還匹配“t#n”、“tpn”甚至“t n”,還有其他許多無意義的組合。這是因爲句點符號匹配所有字符,包括空格、Tab字符甚至換行符: |
1.2 方括號符號 |
爲了解決句點符號匹配範圍過於廣泛這一問題,你可以在方括號(“[]”)裏面指定看來有意義的字符。此時,只有方括號裏面指定的字符才參與匹配。也就是說,正則表達式“t[aeio]n”只匹配“tan”、“Ten”、“tin”和“ton”。但“Toon”不匹配,因爲在方括號之內你只能匹配單個字符: |
1.3 “或”符號 |
如果除了上面匹配的所有單詞之外,你還想要匹配“toon”,那麼,你可以使用“|”操作符。“|”操作符的基本意義就是“或”運算。要匹配“toon”,使用“t(a|e|i|o|oo)n”正則表達式。這裏不能使用方擴號,因爲方括號只允許匹配單個字符;這裏必須使用圓括號“()”。圓括號還可以用來分組,具體請參見後面介紹。 |
1.4 表示匹配次數的符號 |
表一顯示了表示匹配次數的符號,這些符號用來確定緊靠該符號左邊的符號出現的次數: |
|
假設我們要在文本文件中搜索美國的社會安全號碼。這個號碼的格式是999-99-9999。用來匹配它的正則表達式如圖一所示。在正則表達式中,連字符(“-”)有着特殊的意義,它表示一個範圍,比如從0到9。因此,匹配社會安全號碼中的連字符號時,它的前面要加上一個轉義字符“/”。 |
|
圖一:匹配所有123-12-1234形式的社會安全號碼 |
假設進行搜索的時候,你希望連字符號可以出現,也可以不出現——即,999-99-9999和999999999都屬於正確的格式。這時,你可以在連字符號後面加上“?”數量限定符號,如圖二所示: |
|
圖二:匹配所有123-12-1234和123121234形式的社會安全號碼 |
下面我們再來看另外一個例子。美國汽車牌照的一種格式是四個數字加上二個字母。它的正則表達式前面是數字部分“[0-9]{4}”,再加上字母部分“[A-Z]{2}”。圖三顯示了完整的正則表達式。 |
|
圖三:匹配典型的美國汽車牌照號碼,如8836KV |
1.5 “否”符號 |
“^”符號稱爲“否”符號。如果用在方括號內,“^”表示不想要匹配的字符。例如,圖四的正則表達式匹配所有單詞,但以“X”字母開頭的單詞除外。 |
|
圖四:匹配所有單詞,但“X”開頭的除外 |
1.6 圓括號和空白符號 |
假設要從格式爲“June 26, 1951”的生日日期中提取出月份部分,用來匹配該日期的正則表達式可以如圖五所示: |
|
圖五:匹配所有Moth DD,YYYY格式的日期 |
新出現的“/s”符號是空白符號,匹配所有的空白字符,包括Tab字符。如果字符串正確匹配,接下來如何提取出月份部分呢?只需在月份周圍加上一個圓括號創建一個組,然後用ORO API(本文後面詳細討論)提取出它的值。修改後的正則表達式如圖六所示: |
|
圖六:匹配所有Month DD,YYYY格式的日期,定義月份值爲第一個組 |
1.7 其它符號 |
爲簡便起見,你可以使用一些爲常見正則表達式創建的快捷符號。如表二所示: |
表二:常用符號 |
|
例如,在前面社會安全號碼的例子中,所有出現“[0-9]”的地方我們都可以使用“/d”。修改後的正則表達式如圖七所示: |
|
圖七:匹配所有123-12-1234格式的社會安全號碼 |
二、Jakarta-ORO庫 |
有許多源代碼開放的正則表達式庫可供Java程序員使用,而且它們中的許多支持Perl 5兼容的正則表達式語法。我在這裏選用的是Jakarta-ORO正則表達式庫,它是最全面的正則表達式API之一,而且它與Perl 5正則表達式完全兼容。另外,它也是優化得最好的API之一。 |
Jakarta-ORO庫以前叫做OROMatcher,Daniel Savarese大方地把它贈送給了Jakarta Project。你可以按照本文最後參考資源的說明下載它。 |
我首先將簡要介紹使用Jakarta-ORO庫時你必須創建和訪問的對象,然後介紹如何使用Jakarta-ORO API。 |
▲ PatternCompiler對象 |
首先,創建一個Perl5Compiler類的實例,並把它賦值給PatternCompiler接口對象。Perl5Compiler是PatternCompiler接口的一個實現,允許你把正則表達式編譯成用來匹配的Pattern對象。 |
▲ Pattern對象 |
要把正則表達式編譯成Pattern對象,調用compiler對象的compile()方法,並在調用參數中指定正則表達式。例如,你可以按照下面這種方式編譯正則表達式“t[aeio]n”: |
默認情況下,編譯器創建一個大小寫敏感的模式(pattern)。因此,上面代碼編譯得到的模式只匹配“tin”、“tan”、 “ten”和“ton”,但不匹配“Tin”和“taN”。要創建一個大小寫不敏感的模式,你應該在調用編譯器的時候指定一個額外的參數: |
創建好Pattern對象之後,你就可以通過PatternMatcher類用該Pattern對象進行模式匹配。 |
▲ PatternMatcher對象 |
PatternMatcher對象根據Pattern對象和字符串進行匹配檢查。你要實例化一個Perl5Matcher類並把結果賦值給PatternMatcher接口。Perl5Matcher類是PatternMatcher接口的一個實現,它根據Perl 5正則表達式語法進行模式匹配: |
使用PatternMatcher對象,你可以用多個方法進行匹配操作,這些方法的第一個參數都是需要根據正則表達式進行匹配的字符串: |
· boolean matches(String input, Pattern pattern):當輸入字符串和正則表達式要精確匹配時使用。換句話說,正則表達式必須完整地描述輸入字符串。 |
· boolean matchesPrefix(String input, Pattern pattern):當正則表達式匹配輸入字符串起始部分時使用。 |
· boolean contains(String input, Pattern pattern):當正則表達式要匹配輸入字符串的一部分時使用(即,它必須是一個子串)。 |
另外,在上面三個方法調用中,你還可以用PatternMatcherInput對象作爲參數替代String對象;這時,你可以從字符串中最後一次匹配的位置開始繼續進行匹配。當字符串可能有多個子串匹配給定的正則表達式時,用PatternMatcherInput對象作爲參數就很有用了。用PatternMatcherInput對象作爲參數替代String時,上述三個方法的語法如下: |
· boolean matches(PatternMatcherInput input, Pattern pattern) |
· boolean matchesPrefix(PatternMatcherInput input, Pattern pattern) |
· boolean contains(PatternMatcherInput input, Pattern pattern) |
三、應用實例 |
下面我們來看看Jakarta-ORO庫的一些應用實例。 |
3.1 日誌文件處理 |
任務:分析一個Web服務器日誌文件,確定每一個用戶花在網站上的時間。在典型的BEA WebLogic日誌文件中,日誌記錄的格式如下: |
分析這個日誌記錄,可以發現,要從這個日誌文件提取的內容有兩項:IP地址和頁面訪問時間。你可以用分組符號(圓括號)從日誌記錄提取出IP地址和時間標記。 |
首先我們來看看IP地址。IP地址有4個字節構成,每一個字節的值在0到255之間,各個字節通過一個句點分隔。因此,IP地址中的每一個字節有至少一個、最多三個數字。圖八顯示了爲IP地址編寫的正則表達式: |
|
圖八:匹配IP地址 |
IP地址中的句點字符必須進行轉義處理(前面加上“/”),因爲IP地址中的句點具有它本來的含義,而不是採用正則表達式語法中的特殊含義。句點在正則表達式中的特殊含義本文前面已經介紹。 |
日誌記錄的時間部分由一對方括號包圍。你可以按照如下思路提取出方括號裏面的所有內容:首先搜索起始方括號字符(“[”),提取出所有不超過結束方括號字符(“]”)的內容,向前尋找直至找到結束方括號字符。圖九顯示了這部分的正則表達式。 |
|
圖九:匹配至少一個字符,直至找到“]” |
現在,把上述兩個正則表達式加上分組符號(圓括號)後合併成單個表達式,這樣就可以從日誌記錄提取出IP地址和時間。注意,爲了匹配“- -”(但不提取它),正則表達式中間加入了“/s-/s-/s”。完整的正則表達式如圖十所示。 |
|
圖十:匹配IP地址和時間標記 |
現在正則表達式已經編寫完畢,接下來可以編寫使用正則表達式庫的Java代碼了。 |
爲使用Jakarta-ORO庫,首先創建正則表達式字符串和待分析的日誌記錄字符串: |
這裏使用的正則表達式與圖十的正則表達式差不多完全相同,但有一點例外:在Java中,你必須對每一個向前的斜槓(“/”)進行轉義處理。圖十不是Java的表示形式,所以我們要在每個“/”前面加上一個“/”以免出現編譯錯誤。遺憾的是,轉義處理過程很容易出現錯誤,所以應該小心謹慎。你可以首先輸入未經轉義處理的正則表達式,然後從左到右依次把每一個“/”替換成“//”。如果要複檢,你可以試着把它輸出到屏幕上。 |
初始化字符串之後,實例化PatternCompiler對象,用PatternCompiler編譯正則表達式創建一個Pattern對象: |
現在,創建PatternMatcher對象,調用PatternMatcher接口的contain()方法檢查匹配情況: |
接下來,利用PatternMatcher接口返回的MatchResult對象,輸出匹配的組。由於logEntry字符串包含匹配的內容,你可以看到類如下面的輸出: |
3.2 HTML處理實例一 |
下面一個任務是分析HTML頁面內FONT標記的所有屬性。HTML頁面內典型的FONT標記如下所示: |
程序將按照如下形式,輸出每一個FONT標記的屬性: |
在這種情況下,我建議你使用兩個正則表達式。第一個如圖十一所示,它從字體標記提取出“"face="Arial, Serif" size="+2" color="red"”。 |
|
圖十一:匹配FONT標記的所有屬性 |
第二個正則表達式如圖十二所示,它把各個屬性分割成名字-值對。 |
|
圖十二:匹配單個屬性,並把它分割成名字-值對 |
分割結果爲: |
現在我們來看看完成這個任務的Java代碼。首先創建兩個正則表達式字符串,用Perl5Compiler把它們編譯成Pattern對象。編譯正則表達式的時候,指定Perl5Compiler.CASE_INSENSITIVE_MASK選項,使得匹配操作不區分大小寫。 |
接下來,創建一個執行匹配操作的Perl5Matcher對象。 |
假設有一個String類型的變量html,它代表了HTML文件中的一行內容。如果html字符串包含FONT標記,匹配器將返回true。此時,你可以用匹配器對象返回的MatchResult對象獲得第一個組,它包含了FONT的所有屬性: |
接下來創建一個PatternMatcherInput對象。這個對象允許你從最後一次匹配的位置開始繼續進行匹配操作,因此,它很適合於提取FONT標記內屬性的名字-值對。創建PatternMatcherInput對象,以參數形式傳入待匹配的字符串。然後,用匹配器實例提取出每一個FONT的屬性。這通過指定PatternMatcherInput對象(而不是字符串對象)爲參數,反覆地調用PatternMatcher對象的contains()方法完成。PatternMatcherInput對象之中的每一次迭代將把它內部的指針向前移動,下一次檢測將從前一次匹配位置的後面開始。 |
本例的輸出結果如下: |
3.3 HTML處理實例二 |
下面我們來看看另一個處理HTML的例子。這一次,我們假定Web服務器從widgets.acme.com移到了newserver.acme.com。現在你要修改一些頁面中的鏈接: |
執行這個搜索的正則表達式如圖十三所示: |
|
圖十三:匹配修改前的鏈接 |
如果能夠匹配這個正則表達式,你可以用下面的內容替換圖十三的鏈接: |
注意#字符的後面加上了$1。Perl正則表達式語法用$1、$2等表示已經匹配且提取出來的組。圖十三的表達式把所有作爲一個組匹配和提取出來的內容附加到鏈接的後面。 |
現在,返回Java。就象前面我們所做的那樣,你必須創建測試字符串,創建把正則表達式編譯到Pattern對象所必需的對象,以及創建一個PatternMatcher對象: |
接下來,用com.oroinc.text.regex包Util類的substitute()靜態方法進行替換,輸出結果字符串: |
Util.substitute()方法的語法如下: |
這個調用的前兩個參數是以前創建的PatternMatcher和Pattern對象。第三個參數是一個Substiution對象,它決定了替換操作如何進行。本例使用的是Perl5Substitution對象,它能夠進行Perl5風格的替換。第四個參數是想要進行替換操作的字符串,最後一個參數允許指定是否替換模式的所有匹配子串(Util.SUBSTITUTE_ALL),或只替換指定的次數。 |
【結束語】在這篇文章中,我爲你介紹了正則表達式的強大功能。只要正確運用,正則表達式能夠在字符串提取和文本修改中起到很大的作用。另外,我還介紹瞭如何在Java程序中通過Jakarta-ORO庫利用正則表達式。至於最終採用老式的字符串處理方式(使用StringTokenizer,charAt,和substring),還是採用正則表達式,這就有待你自己決定了。 |
Jakarta-ORO篇
陳廣佳 ([email protected])
<!-- Author name (email
address)
-->電子信息工程系工科學士
2001 年 12 月
由於工作的需要,本人經常要面對大量的文字電子資料的整理工作,因此曾對在JAVA中正則表達式的應用有所關注,並對其有一定的瞭解,希望通過本文與同行進行有關方面的心得交流。
正則表達式:
正則表達式是一種可以用於模式匹配和替換的強有力的工具,一個正則表達式就是由普通的字符(例如字符 a 到 z)以及特殊字符(稱爲元字符)組成的文字模式,它描述在查找文字主體時待匹配的一個或多個字符串。正則表達式作爲一個模板,將某個字符模式與所搜索的字符串進行匹配。
正則表達式在字符數據處理中起着非常重要的作用,我們可以用正則表達式完成大部分的數據分析處理工作,如:判斷一個串是否是數字、是否是有效的Email地址,從海量的文字資料中提取有價值的數據等等,如果不使用正則表達式,那麼實現的程序可能會很長,並且容易出錯。對這點本人深有體會,面對大量工具書電子檔資料的整理工作,如果不懂得應用正則表達式來處理,那麼將是很痛苦的一件事情,反之則將可以輕鬆地完成,獲得事半功倍的效果。
由於本文目的是要介紹如何在JAVA裏運用正則表達式,因此對剛接觸正則表達式的讀者請參考有關資料,在此因篇幅有限不作介紹。
JAVA對正則表達式的支持:
在JDK1.3或之前的JDK版本中並沒有包含正則表達式庫可供JAVA程序員使用,之前我們一般都在使用第三方提供的正則表達式庫,這些第三方庫中有源代碼開放的,也有需付費購買的,而現時在JDK1.4的測試版中也已經包含有正則表達式庫---java.util.regex。
故此現在我們有很多面向JAVA的正則表達式庫可供選擇,以下我將介紹兩個較具代表性的 Jakarta-ORO和java.util.regex,首先當然是本人一直在用的Jakarta-ORO:
1.簡介:
Jakarta-ORO是最全面以及優化得最好的正則表達式API之一,Jakarta-ORO庫以前叫做OROMatcher,是由Daniel F. Savarese編寫,後來他將其贈與Jakarta Project,讀者可在Apache.org的網站下載該API包。
許多源代碼開放的正則表達式庫都是支持Perl5兼容的正則表達式語法,Jakarta-ORO正則表達式庫也不例外,他與Perl 5正則表達式完全兼容。
2.對象與其方法:
★PatternCompiler對象:
我們在使用Jakarta-ORO API包時,最先要做的是,創建一個Perl5Compiler類的實例,並把它賦值給PatternCompiler接口對象。Perl5Compiler是PatternCompiler接口的一個實現,允許你把正則表達式編譯成用來匹配的Pattern對象。
PatternCompiler compiler=new Perl5Compiler();
★Pattern對象:
要把所對應的正則表達式編譯成Pattern對象,需要調用compiler對象的compile()方法,並在調用參數中指定正則表達式。舉個例子,你可以按照下面這種方式編譯正則表達式"s[ahkl]y":
|
在默認的情況下,編譯器會創建一個對大小寫敏感的模式(pattern)。因此,上面代碼編譯得到的模式只匹配"say"、"shy"、 "sky"和"sly",但不匹配"Say"和"skY"。要創建一個大小寫不敏感的模式,你應該在調用編譯器的時候指定一個額外的參數:
pattern=compiler.compile("s[ahkl]y",Perl5Compiler.CASE_INSENSITIVE_MASK);
Pattern對象創建好之後,就可以通過PatternMatcher類用該Pattern對象進行模式匹配。
★PatternMatcher對象:
PatternMatcher對象依據Pattern對象和字符串展開匹配檢查。你要實例化一個Perl5Matcher類並把結果賦值給PatternMatcher接口。Perl5Matcher類是PatternMatcher接口的一個實現,它根據Perl 5正則表達式語法進行模式匹配:
PatternMatcher matcher=new Perl5Matcher();
PatternMatcher對象提供了多個方法進行匹配操作,這些方法的第一個參數都是需要根據正則表達式進行匹配的字符串:
- boolean matches(String input, Pattern pattern):當要求輸入的字符串input和正則表達式pattern精確匹配時使用該方法。也就是說當正則表達式完整地描述輸入字符串時返回真值。
- boolean matchesPrefix(String input, Pattern pattern):要求正則表達式匹配輸入字符串起始部分時使用該方法。也就是說當輸入字符串的起始部分與正則表達式匹配時返回真值。
- boolean contains(String input, Pattern pattern):當正則表達式要匹配輸入字符串的一部分時使用該方法。當正則表達式爲輸入字符串的子串時返回真值。
但以上三種方法只會查找輸入字符串中匹配正則表達式的第一個對象,如果當字符串可能有多個子串匹配給定的正則表達式時,那麼你就可以在調用上面三個方法時用PatternMatcherInput對象作爲參數替代String對象,這樣就可以從字符串中最後一次匹配的位置開始繼續進行匹配,這樣就方便的多了。
用PatternMatcherInput對象作爲參數替代String時,上述三個方法的語法如下:
- boolean matches(PatternMatcherInput input, Pattern pattern)
- boolean matchesPrefix(PatternMatcherInput input, Pattern pattern)
- boolean contains(PatternMatcherInput input, Pattern pattern)
★Util.substitute()方法:
查找後需要要進行替換,我們就要用到Util.substitute()方法,其語法如下:
|
前兩個參數分別爲PatternMatcher和Pattern對象。而第三個參數是個Substiution對象,由它來決定替換操作如何進行。第四個參數是要進行替換操作的目標字符串,最後一個參數用來指定是否替換模式的所有匹配子串(Util.SUBSTITUTE_ALL),或只進行指定次數的替換。
在這裏我相信有必要詳細解說一下第三個參數Substiution對象,因爲它將決定替換將怎樣進行。
Substiution:
Substiution是一個接口類,它爲你提供了在使用Util.substitute()方法時控制替換方式的手段,它有兩個標準的實現類:StringSubstitution與Perl5Substitution。當然,同時你也可以生成自己的實現類來定製你所需要的特殊替換動作。
StringSubstitution:
StringSubstitution 實現的是簡單的純文字替換手段,它有兩個構造方法:
StringSubstitution()->缺省的構造方法,初始化一個包含零長度字符串的替換對象。
StringSubstitution(java.lang.String substitution)->初始化一個給定字符串的替換對象。
Perl5Substitution:
Perl5Substitution 是StringSubstitution的子類,它在實現純文字替換手段的同時也允許進行鍼對MATH類裏各匹配組的PERL5變量的替換,所以他的替換手段比其直接父類StringSubstitution更爲多元化。
它有三個構造器:
Perl5Substitution()
Perl5Substitution(java.lang.String substitution)
Perl5Substitution(java.lang.String substitution, int numInterpolations)
前兩種構造方法與StringSubstitution一樣,而第三種構造方法下面將會介紹到。
在Perl5Substitution的替換字符串中可以包含用來替代在正則表達式裏由小擴號圍起來的匹配組的變量,這些變量是由$1, $2,$3等形式來標識。我們可以用一個例子來解釋怎樣使用替換變量來進行替換:
假設我們有正則表達式模式爲b/d+:(也就是b[0-9]+:),而我們想把所有匹配的字符串中的"b"都改爲"a",而":"則改爲"-",而其餘部分則不作修改,如我們輸入字符串爲"EXAMPLE b123:",經過替換後就應該變成"EXAMPLE a123-"。要做到這點,我們就首先要把不做替換的部分用分組符號小括號包起來,這樣正則表達式就變爲"b(/d+):",而構造Perl5Substitution對象時其替換字符串就應該是"a$1-",也就是構造式爲Perl5Substitution("a$1-"),表示在使用Util.substitute()方法時只要在目標字符串裏找到和正則表達式" b(/d+): "相匹配的子串都用替換字符串來替換,而變量$1表示如果和正則表達式裏第一個組相匹配的內容則照般原文插到$1所在的爲置,如在"EXAMPLE b123:"中和正則表達式相匹配的部分是"b123:",而其中和第一分組"(/d+)"相匹配的部分則是"123",所以最後替換結果爲"EXAMPLE a123-"。
有一點需要清楚的是,如果你把構造器Perl5Substitution(java.lang.String substitution,int numInterpolations)
中的numInterpolations參數設爲INTERPOLATE_ALL,那麼當每次找到一個匹配字串時,替換變量($1,$2等)所指向的內容都根據目前匹配字串來更新,但是如果numInterpolations參數設爲一個正整數N時,那麼在替換時就只會在前N次匹配發生時替換變量會跟隨匹配對象來調整所代表的內容,但N次之後就以一致以第N次替換變量所代表內容來做爲以後替換結果。
舉個例子會更好理解:
假如沿用以上例子中的正則表達式模式以及替換內容來進行替換工作,設目標字符串爲"Tank b123: 85 Tank b256: 32 Tank b78: 22",並且設numInterpolations參數爲INTERPOLATE_ALL,而Util.substitute()方法中的numSub變量設爲SUBSTITUTE_ALL(請參考上文Util.substitute()方法內容),那麼你獲得的替換結果將會是:
Tank a123- 85 Tank a256- 32 Tank a78- 22
但是如果你把numInterpolations設爲2,並且numSubs依然設爲SUBSTITUTE_ALL,那麼這時你獲得的結果則會是:
Tank a123- 85 Tank a256- 32 Tank a256- 22
你要注意到最後一個替換所用變量$1所代表的內容與第二個$1一樣爲"256",而不是預期的"78",因爲在替換進行中,替換變量$1只根據匹配內容進行了兩次更新,最後一次就使第二次匹配時所更新的結果,那麼我們可以由此知道,如果numInterpolations設爲1,那麼結果將是:
Tank a123- 85 Tank a123- 32 Tank a123- 22
3.應用示例:
剛好前段時間公司準備出一個《伊索預言》的英語學習互動教材,其中有電子檔資料的整理工作,我們就以此爲例來看一下Jakarta-ORO與JDBC2.0 API結合起來對數據庫內的資料進行簡單提取與整理的實現。假設由錄入部的同事送過來的存放在MS SQLSERVER 7數據庫裏的電子檔的表結構如下(注:或許在不同的DBMS中有相應的正則表達式的應用,但這不在本文討論範圍內):
表名:AESOP, 表中每條記錄包含有三列:
ID(int):單詞索引號
WORD(varchar):單詞
CONTENT(varchar):存放單詞的相關解釋與例句等內容
其中CONTENT列中內容的格式如下:
[音標] [詞性] (解釋){(例句一/例句解釋/例句中該詞的詞性: 單詞在句中的意思) (例句二/例句解釋/例句中該詞的詞性: 單詞在句中的意思)}
如對應單詞Kevin,CONTENT中的內容如下:
['kevin] [名詞](人名凱文){(Kevin loves comic./凱文愛漫畫/名詞: 凱文)( Kevin is living in ZhuHai now./凱文現住在珠海/名詞: 凱文)}
我們的例子主要針對CONTENT列中內容進行字符串處理。
★查找單個匹配:
首先,讓我們嘗試把CONTNET列中的[音標]字段的內容列示出來,由於所有單詞的記錄中都有這一項並且都在字串開始位置,所以這個查找工作比較簡單:
- 確定相應的正則表達式:/[[^]]+/]
這個是很簡單的正則表達式,其意思是要求相匹配的字符串必須爲以一對中括號包含的所有內容,如['kevin] 、[名詞]等,但內容中不包括"]"符號,也就是要避免出現"[][]"會作爲一個匹配對象的情況出現(有關正則表達式的基礎知識請參照有關資料,這裏不再詳述)。
注意,在Java中,你必須對每一個向前的斜槓("/")進行轉義處理。所以我們要在上面的正則表達式裏每個"/"前面加上一個"/"以免出現編譯錯誤,也就是在JAVA中初始化正則表達式的字符串的語句應該爲:
String restring=" //[[^]]+//]";
並且在表達式裏每個符號中間不能有空格,否則就會同樣出現編譯錯誤。
- 實例化PatternCompiler對象,創建Pattern對象
PatternCompiler compiler=new Perl5Compiler();
Pattern pattern=compiler.compile(restring);
- 創建PatternMatcher對象,調用PatternMatcher接口的contain()方法檢查匹配情況:
PatternMatcher matcher=new Perl5Matcher(); if (matcher.contains(content,pattern)) { //處理代碼片段 }
這裏matcher.contains(content,pattern)中的參數 content是從數據庫裏取來的字符串變量。該方法只會查到第一個匹配的對象字符串,但是由於音標項均在CONETNET內容字符串中的起始位置,所以用這個方法就已經可以保證把每條記錄裏的音標項找出來了,但更爲直接與合理的辦法是使用boolean matchesPrefix(PatternMatcherInput input, Pattern pattern)方法,該方法驗證目標字符串是否以正則表達式所匹配的字串爲起始。
具體實現的完整的程序代碼如下:
package RegularExpressions; //import…… import org.apache.oro.text.regex.*; //使用Jakarta-ORO正則表達式庫前需要把它加到CLASSPATH裏面,如果用IDE是//JBUILDER,那麼也可以在JBUILDER裏直接自建新庫。 public class yisuo{ public static void main(String[] args){ try{ //使用JDBC DRIVER進行DBMS連接,這裏我使用的是一個第三方JDBC //DRIVER,Microsoft本身也有一個面向SQLSERVER7/2000的免費JDBC //DRIVER,但其性能真的是奇差,不用也罷。 Class.forName("com.jnetdirect.jsql.JSQLDriver"); Connection con=DriverManager.getConnection ("jdbc:JSQLConnect://kevin:1433","kevin chen","re"); Statement stmt = con.createStatement(ResultSet.TYPE_SCROLL_SENSITIVE, ResultSet.CONCUR_UPDATABLE); //爲使用Jakarta-ORO庫而創建相應的對象 String rsstring=" //[[^]]+//]"; PatternCompiler orocom=new Perl5Compiler(); Pattern pattern=orocom.compile(rsstring); PatternMatcher matcher=new Perl5Matcher(); ResultSet uprs = stmt.executeQuery("SELECT * FROM aesop"); while (uprs.next()) { Stirng word=uprs.getString("word"); Stirng content=uprs.getString("content"); if(matcher.contains(content,pattern)){ //或if(matcher.matchesPrefix(content,pattern)){ MatchResult result=matcher.getMatch(); Stirng pure=result.toString(); System.out.println(word+"的音標爲:"+pure); } } } catch(Exception e) { System.out.println(e); } } }
輸出結果爲:kevin的音標爲['kevin]
在這個處理中我是用toString()方法來取得結果,但是如果正則表達式裏是用了分組符號(圓括號),那麼就可以用group(int gid)的方法來取得相應各組匹配的結果,如正則表達式改爲" (/[[^]]+/])",那麼就可以用以下方法來取得結果:pure=result.group(0);
用程序驗證,輸出結果同樣爲:kevin的音標爲['kevin]
而如果正則表達式爲(/[[^]]+/])(/[[^]]+/]),則會查找到兩個連續的方括號所包含的內容,也就找到[音標] [詞性]兩項,但是兩項的結果分別在兩個組裏面,分別由下面語句獲得結果:
result.group(0)->返回[音標] [詞性]兩項內容,也就是與整個正則表達式相匹配的結果字符串,在這裏也就爲['kevin] [名詞]
result.group(1) ->返回[音標]項內容,結果應是['kevin]
result.group(2) ->返回[詞性]項內容,結果應是[名詞]
繼續用程序驗證,發現輸出並不正確,主要是當內容有中文時就不能成功匹配,考慮到可能是Jakarta-ORO正則表達式庫版本不支持中文的問題,回看一下原來我一直用的還是2.0.1的老版本,馬上到Jakarta.org上下載最新的2.0.4版本裝上再用程序驗證,得出的結果就和預期一樣正確。
★查找多個匹配:
經過第一步的嘗試使用Jakarta-ORO後,我們已經知道了如何正確使用該API包來查找目標字符串裏一個匹配的子串,下面我們接着來看一看當目標字符串裏包含不止一個匹配的子串時我們如何把它們一個接一個找出來進行相應的處理。
首先我們先試個簡單的應用,假設我們想把CONTNET字段內容裏所有用方括號包起來的字串都找出來,很清楚地,CONTNET字段的內容裏面就只有兩項匹配的內容:[音標]和 [詞性],剛纔我們其實已經把它們分別找出來了,但是我們所用的方法是分組方法,把"[音標] [詞性]"作爲一整個正則表達式匹配的內容先找到,再根據分組把[音標]和 [詞性]分別挑出來。但是現在我們需要做的是把[音標]和[詞性]分別做爲與同一個正則表達式匹配的內容,先找到一個接着再找下一個,也就是剛纔我們的表達式爲(/[[^]]+/])(/[[^]]+/]),而現在應爲" /[[^]]+/] "。
我們已經知道在匹配操作的三個方法裏只要用PatternMatcherInput對象作爲參數替代String對象就可以從字符串中最後一次匹配的位置開始繼續進行匹配,實現的程序片段如下:
|
[名詞]
接着我們來做複雜一點的處理,就是我們要先把下面內容:
['kevin] [名詞](人名凱文){(Kevin loves comic./凱文愛漫畫/名詞: 凱文)( Kevin is living in ZhuHai now. /凱文現住在珠海/名詞: 凱文)}中的整個例句部分(也就是由大括號所包含的部分)找出來,再分別把例句一和例句二找出,而各例句中的各項內容(英文句、中文句、詞性、解釋)也要分項列出。
第一步當然是要定出相應的正則表達式,需要有兩個,一是和整個例句部分(也就是由大括號包起來的部分)匹配的正則表達式:"/{.+/}",
另一個則要和每個例句部分匹配(也就是小括號中的內容),:/(([^)]+/)
而且由於要把例句的各項分離出來,所以要再把裏面的各部分用分組的方法匹配出來:" ([^(]+)/(.+)/(.+):([^)]+) "。
爲了簡便起見,我們不再和從數據庫裏讀出,而是構造一個包含同樣內容的字符串變量,程序片段如下:
|
輸出結果爲:
英文句: Kevin loves comic.
句子中文翻譯: 凱文愛漫畫
詞性: 名詞
意思: 凱文
英文句: Kevin is living in ZhuHai now.
句子中文翻譯: 凱文現住在珠海
詞性: 名詞
意思: 凱文
★查找替換:
以上的兩個應用都是單純在查找字符串匹配方面的,我們再來看一下查找後如何對目標字符串進行替換。
例如我現在想把第二個例句進行改動,換爲:Kevin has seen《LEON》seveal times,because it is a good film./ 凱文已經看過《這個殺手不太冷》幾次了,因爲它是一部好電影。/名詞:凱文。
也就是把
['kevin] [名詞](人名凱文){(Kevin loves comic./凱文愛漫畫/名詞: 凱文)( Kevin is living in ZhuHai now. /凱文現住在珠海/名詞: 凱文)}
改爲:
['kevin] [名詞](人名凱文){(Kevin loves comic./凱文愛漫畫/名詞: 凱文)( Kevin has seen《LEON》seveal times,because it is a good film./ 凱文已經看過《這個殺手不太冷》幾次了,因爲它是一部好電影。/名詞:凱文。)}
之前,我們已經瞭解Util.substitute()方法與Substiution接口,以及Substiution的兩個實現類StringSubstitution和Perl5Substitution,我們就來看看怎麼用Util.substitute()方法配合Perl5Substitution來完成我們上面提出的替換要求,確定正則表達式:
我們要先找到其中的整個例句部分,也就是由大括號包起來的字串,並且把兩個例句分別分組,所以正則表達式爲:"/{(/([^)]+/))(/([^)]+/))/}",如果用替換變量來代替分組,那麼上面的表達式可以看爲"/{$1$2/}",這樣就可以更容易看出替換變量與分組間的關係。
根據上面的正則表達式Perl5Substitution類可以這樣構造:
Perl5Substitution("{$1( Kevin has seen《LEON》seveal times,because it is a good film./ 凱文已經看過《這個殺手不太冷》幾次了,因爲它是一部好電影。/名詞:凱文。)}")
再根據這個Perl5Substitution對象來使用Util.substitute()方法便可以完成替換了,實現的代碼片段如下:
|
輸出結果是正確的,爲:
['kevin] [名詞](人名凱文){(Kevin loves comic./凱文愛漫畫/名詞: 凱文)( Kevin has seen《LEON》seveal times,because it is a good film./ 凱文已經看過《這個殺手不太冷》幾次了,因爲它是一部好電影。/名詞:凱文。)}
至於有關使用numInterpolations參數的構造器用法,讀者只要根據上面的介紹自己動手試一下就會清楚了,在此就不再例述。
總結:
本文首先介紹了Jakarta-ORO正則表達式庫的對象與方法,並且接着舉例讓讀者對實際應用有進一步的瞭解,雖然例子都比較簡單,但希望讀者們在看了該文後對Jakarta-ORO正則表達式庫有一定的認知,在實際工作中有所幫助與啓發。
其實在Jakarta org裏除了Jakarta-ORO外還有一個百分百的純JAVA正則表達式庫,就是由Jonathan Locke贈與Jakarta ORG的Regexp,在該包裏面包含了完整的文檔以及一個用於調試的Applet例子,對其有興趣的讀者可以到此下載。
- 本文的主要參考文章,該文在介紹Jakarta-ORO的同時也爲讀者詳盡解析了正則表達式的基本語法。
- 一個基於PERL的正則表達式詳盡教程(雖然該教程是基於PERL的,但是你並不需要有PERL的經驗,雖然那會有所幫助),以及一個不錯的正則表達式簡例教程。
- 最不可缺少的當然是Jakarta-ORO的幫助文檔http://jakarta.apache.org/oro/api/
關於作者 陳廣佳 Kevin Chen,汕頭大學電子信息工程系工科學士,臺灣大新出版社珠海區開發部,現正圍繞中日韓電子資料使用JAVA開發電子詞典等相關項目。可通過E-mail:[email protected]於他聯繫。 |
java.util.regex篇
陳廣佳 ([email protected])
<!-- Author name (email
address)
-->電子信息工程系工科學士
2001 年 12 月
現在JDK1.4裏終於有了自己的正則表達式API包,JAVA程序員可以免去找第三方提供的正則表達式庫的周折了,我們現在就馬上來了解一下這個SUN提供的遲來恩物- -對我來說確實如此。
1.簡介:
java.util.regex是一個用正則表達式所訂製的模式來對字符串進行匹配工作的類庫包。
它包括兩個類:Pattern和Matcher
Pattern | 一個Pattern是一個正則表達式經編譯後的表現模式。 |
Matcher | 一個Matcher對象是一個狀態機器,它依據Pattern對象做爲匹配模式對字符串展開匹配檢查。 |
首先一個Pattern實例訂製了一個所用語法與PERL的類似的正則表達式經編譯後的模式,然後一個Matcher實例在這個給定的Pattern實例的模式控制下進行字符串的匹配工作。
以下我們就分別來看看這兩個類:
2.Pattern類:
Pattern的方法如下:
static Pattern | compile(String regex) 將給定的正則表達式編譯並賦予給Pattern類 |
static Pattern | compile(String regex, int flags) 同上,但增加flag參數的指定,可選的flag參數包括:CASE INSENSITIVE,MULTILINE,DOTALL,UNICODE CASE, CANON EQ |
int | flags() 返回當前Pattern的匹配flag參數. |
Matcher | matcher(CharSequence input) 生成一個給定命名的Matcher對象 |
static boolean | matches(String regex, CharSequence input) 編譯給定的正則表達式並且對輸入的字串以該正則表達式爲模開展匹配,該方法適合於該正則表達式只會使用一次的情況,也就是隻進行一次匹配工作,因爲這種情況下並不需要生成一個Matcher實例。 |
String | pattern() 返回該Patter對象所編譯的正則表達式。 |
String[] | split(CharSequence input) 將目標字符串按照Pattern裏所包含的正則表達式爲模進行分割。 |
String[] | split(CharSequence input, int limit) 作用同上,增加參數limit目的在於要指定分割的段數,如將limi設爲2,那麼目標字符串將根據正則表達式分爲割爲兩段。 |
一個正則表達式,也就是一串有特定意義的字符,必須首先要編譯成爲一個Pattern類的實例,這個Pattern對象將會使用 matcher()方法來生成一個Matcher實例,接着便可以使用該 Matcher實例以編譯的正則表達式爲基礎對目標字符串進行匹配工作,多個Matcher是可以共用一個Pattern對象的。
現在我們先來看一個簡單的例子,再通過分析它來了解怎樣生成一個Pattern對象並且編譯一個正則表達式,最後根據這個正則表達式將目標字符串進行分割:
|
輸出結果爲:
Kevin has seen《LEON》seveal times,because it is a good film.
凱文已經看過《這個殺手不太冷》幾次了,因爲它是一部好電影。
名詞:凱文。
很明顯,該程序將字符串按"/"進行了分段,我們以下再使用 split(CharSequence input, int limit)方法來指定分段的段數,程序改動爲:
tring[] result = p.split("Kevin has seen《LEON》seveal times,because it is a good film./ 凱文已經看過《這個殺手不太冷》幾次了,因爲它是一部好電影。/名詞:凱文。",2);
這裏面的參數"2"表明將目標語句分爲兩段。
輸出結果則爲:
Kevin has seen《LEON》seveal times,because it is a good film.
凱文已經看過《這個殺手不太冷》幾次了,因爲它是一部好電影。/名詞:凱文。
由上面的例子,我們可以比較出java.util.regex包在構造Pattern對象以及編譯指定的正則表達式的實現手法與我們在上一篇中所介紹的Jakarta-ORO 包在完成同樣工作時的差別,Jakarta-ORO 包要先構造一個PatternCompiler類對象接着生成一個Pattern對象,再將正則表達式用該PatternCompiler類的compile()方法來將所需的正則表達式編譯賦予Pattern類:
PatternCompiler orocom=new Perl5Compiler();
Pattern pattern=orocom.compile("REGULAR EXPRESSIONS");
PatternMatcher matcher=new Perl5Matcher();
但是在java.util.regex包裏,我們僅需生成一個Pattern類,直接使用它的compile()方法就可以達到同樣的效果:
Pattern p = Pattern.compile("[/]+");
因此似乎java.util.regex的構造法比Jakarta-ORO更爲簡潔並容易理解。
3.Matcher類:
Matcher方法如下:
Matcher | appendReplacement(StringBuffer sb, String replacement) 將當前匹配子串替換爲指定字符串,並且將替換後的子串以及其之前到上次匹配子串之後的字符串段添加到一個StringBuffer對象裏。 |
StringBuffer | appendTail(StringBuffer sb) 將最後一次匹配工作後剩餘的字符串添加到一個StringBuffer對象裏。 |
int | end() 返回當前匹配的子串的最後一個字符在原目標字符串中的索引位置 。 |
int | end(int group) 返回與匹配模式裏指定的組相匹配的子串最後一個字符的位置。 |
boolean | find() 嘗試在目標字符串裏查找下一個匹配子串。 |
boolean | find(int start) 重設Matcher對象,並且嘗試在目標字符串裏從指定的位置開始查找下一個匹配的子串。 |
String | group() 返回當前查找而獲得的與組匹配的所有子串內容 |
String | group(int group) 返回當前查找而獲得的與指定的組匹配的子串內容 |
int | groupCount() 返回當前查找所獲得的匹配組的數量。 |
boolean | lookingAt() 檢測目標字符串是否以匹配的子串起始。 |
boolean | matches() 嘗試對整個目標字符展開匹配檢測,也就是隻有整個目標字符串完全匹配時才返回真值。 |
Pattern | pattern() 返回該Matcher對象的現有匹配模式,也就是對應的Pattern 對象。 |
String | replaceAll(String replacement) 將目標字符串裏與既有模式相匹配的子串全部替換爲指定的字符串。 |
String | replaceFirst(String replacement) 將目標字符串裏第一個與既有模式相匹配的子串替換爲指定的字符串。 |
Matcher | reset() 重設該Matcher對象。 |
Matcher | reset(CharSequence input) 重設該Matcher對象並且指定一個新的目標字符串。 |
int | start() 返回當前查找所獲子串的開始字符在原目標字符串中的位置。 |
int | start(int group) 返回當前查找所獲得的和指定組匹配的子串的第一個字符在原目標字符串中的位置。 |
(光看方法的解釋是不是很不好理解?不要急,待會結合例子就比較容易明白了)
一個Matcher實例是被用來對目標字符串進行基於既有模式(也就是一個給定的Pattern所編譯的正則表達式)進行匹配查找的,所有往Matcher的輸入都是通過CharSequence接口提供的,這樣做的目的在於可以支持對從多元化的數據源所提供的數據進行匹配工作。
我們分別來看看各方法的使用:
★matches()/lookingAt ()/find():
一個Matcher對象是由一個Pattern對象調用其matcher()方法而生成的,一旦該Matcher對象生成,它就可以進行三種不同的匹配查找操作:
- matches()方法嘗試對整個目標字符展開匹配檢測,也就是隻有整個目標字符串完全匹配時才返回真值。
- lookingAt ()方法將檢測目標字符串是否以匹配的子串起始。
- find()方法嘗試在目標字符串裏查找下一個匹配子串。
以上三個方法都將返回一個布爾值來表明成功與否。
★replaceAll ()/appendReplacement()/appendTail():
Matcher類同時提供了四個將匹配子串替換成指定字符串的方法:
- replaceAll()
- replaceFirst()
- appendReplacement()
- appendTail()
replaceAll()與replaceFirst()的用法都比較簡單,請看上面方法的解釋。我們主要重點了解一下appendReplacement()和appendTail()方法。
appendReplacement(StringBuffer sb, String replacement) 將當前匹配子串替換爲指定字符串,並且將替換後的子串以及其之前到上次匹配子串之後的字符串段添加到一個StringBuffer對象裏,而appendTail(StringBuffer sb) 方法則將最後一次匹配工作後剩餘的字符串添加到一個StringBuffer對象裏。
例如,有字符串fatcatfatcatfat,假設既有正則表達式模式爲"cat",第一次匹配後調用appendReplacement(sb,"dog"),那麼這時StringBuffer sb的內容爲fatdog,也就是fatcat中的cat被替換爲dog並且與匹配子串前的內容加到sb裏,而第二次匹配後調用appendReplacement(sb,"dog"),那麼sb的內容就變爲fatdogfatdog,如果最後再調用一次appendTail(sb),那麼sb最終的內容將是fatdogfatdogfat。
還是有點模糊?那麼我們來看個簡單的程序:
|
最終輸出結果爲:
第1次匹配後sb的內容是:Kevin
第2次匹配後sb的內容是:Kevin Li and Kevin
第3次匹配後sb的內容是:Kevin Li and Kevin Chan are both working in Kevin
第4次匹配後sb的內容是:Kevin Li and Kevin Chan are both working in Kevin Chen's Kevin
調用m.appendTail(sb)後sb的最終內容是:Kevin Li and Kevin Chan are both working in Kevin Chen's KevinSoftShop company.
看了上面這個例程是否對appendReplacement(),appendTail()兩個方法的使用更清楚呢,如果還是不太肯定最好自己動手寫幾行代碼測試一下。
★group()/group(int group)/groupCount():
該系列方法與我們在上篇介紹的Jakarta-ORO中的MatchResult .group()方法類似(有關Jakarta-ORO請參考上篇的內容),都是要返回與組匹配的子串內容,下面代碼將很好解釋其用法:
|
輸出爲:
該次查找獲得匹配組的數量爲:2
第1組的子串內容爲:ca
第2組的子串內容爲:t
Matcher對象的其他方法因比較好理解且由於篇幅有限,請讀者自己編程驗證。
4.一個檢驗Email地址的小程序:
最後我們來看一個檢驗Email地址的例程,該程序是用來檢驗一個輸入的EMAIL地址裏所包含的字符是否合法,雖然這不是一個完整的EMAIL地址檢驗程序,它不能檢驗所有可能出現的情況,但在必要時您可以在其基礎上增加所需功能。
|
例如,我們在命令行輸入:java Email [email protected]
那麼輸出結果將會是:EMAIL地址不能以'www.'起始
如果輸入的EMAIL爲@[email protected]
則輸出爲:EMAIL地址不能以'.'或'@'作爲起始字符
當輸入爲:cgjmail#$%@163.net
那麼輸出就是:
輸入的EMAIL地址裏包含有冒號、逗號等非法字符,請修改
您現在的輸入爲: cgjmail#$%@163.net
修改後合法的地址應類似: [email protected]
5.總結:
本文介紹了jdk1.4.0-beta3里正則表達式庫--java.util.regex中的類以及其方法,如果結合與上一篇中所介紹的Jakarta-ORO API作比較,讀者會更容易掌握該API的使用,當然該庫的性能將在未來的日子裏不斷擴展,希望獲得最新信息的讀者最好到及時到SUN的網站去了解。
6.結束語:
本來計劃再多寫一篇介紹一下需付費的正則表達式庫中較具代表性的作品,但覺得既然有了免費且優秀的正則表達式庫可以使用,何必還要去找需付費的呢,相信很多讀者也是這麼想的:,所以有興趣瞭解更多其他的第三方正則表達式庫的朋友可以自己到網上查找或者到我在參考資料裏提供的網址去看看。
- java.util.regex的幫助文檔
- Dana Nourie 和Mike McCloskey所寫的Regular Expressions and the Java" Programming Language
- 需要更多的第三方正則表達式資源以及基於它們所開發的應用程序請看http://www.meurrens.org/ip-Links/java/regex/index.html
關於作者 陳廣佳 Kevin Chen,汕頭大學電子信息工程系工科學士,臺灣大新出版社珠海區開發部,現正圍繞中日韓電子資料使用JAVA開發電子詞典等相關項目。可通過E-mail:[email protected]於他聯繫。 |