源文:http://www.ccw.com.cn/htm/app/aprog/01_7_31_4.asp
| 我們先從簡單的開始。假設你要搜尋一個包含字元“cat”的字串,搜尋用的Regex就是“cat”。如果搜尋對大小寫不敏感,單詞“catalog”、“Catherine”、“sophisticated”都可以匹配。也就是說: |
| 假設你在玩英文拼字遊戲,想要找出三個字母的單詞,而且這些單詞必須以“t”字母開頭,以“n”字母結束。另外,假設有一本英文字典,你可以用Regex搜尋它的全部內容。要構造出這個Regex,你可以使用一個萬用字元——句點符號“.”。這樣,完整的運算式就是 “t.n”,它匹配“tan”、“ten”、“tin”和“ton”,還匹配“t#n”、“tpn”甚至“t n”,還有其他許多無意義的組合。這是因為句點符號匹配所有字元,包括空格、Tab字元甚至分行符號: |
| 為瞭解決句點符號匹配範圍過於廣泛這一問題,你可以在方括弧(“[]”)裡面指定看來有意義的字元。此時,只有方括弧裡面指定的字元才參與匹配。也就是說,Regex“t[aeio]n”只匹配“tan”、“Ten”、“tin”和“ton”。但“Toon”不匹配,因為在方括弧之內你只能匹配單個字元: |
| 如果除了上面匹配的所有單詞之外,你還想要匹配“toon”,那麼,你可以使用“|”操作符。“|”操作符的基本意義就是“或”運算。要匹配“toon”,使用“t(a|e|i|o|oo)n”Regex。這裡不能使用方擴號,因為方括弧只允許匹配單個字元;這裡必須使用圓括弧“()”。圓括弧還可以用來分組,具體請參見後面介紹。 |
| 表一顯示了表示匹配次數的符號,這些符號用來確定緊靠該符號左邊的符號出現的次數: |
| 假設我們要在文字檔中搜尋美國的社會安全號碼。這個號碼的格式是999-99-9999。用來匹配它的Regex一所示。在Regex中,連字號(“-”)有著特殊的意義,它表示一個範圍,比如從0到9。因此,匹配社會安全號碼中的連字號號時,它的前面要加上一個逸出字元“\”。 |
圖一:匹配所有123-12-1234形式的社會安全號碼 |
| 假設進行搜尋的時候,你希望連字號號可以出現,也可以不出現——即,999-99-9999和999999999都屬於正確的格式。這時,你可以在連字號號後面加上“?”數量限定符號,二所示: |
圖二:匹配所有123-12-1234和123121234形式的社會安全號碼 |
| 下面我們再來看另外一個例子。美國汽車牌照的一種格式是四個數字加上二個字母。它的Regex前面是數字部分“[0-9]{4}”,再加上字母部分“[A-Z]{2}”。圖三顯示了完整的Regex。 |
| “^”符號稱為“否”符號。如果用在方括弧內,“^”表示不想要匹配的字元。例如,圖四的Regex匹配所有單詞,但以“X”字母開頭的單詞除外。 |
| 假設要從格式為“June 26, 1951”的生日日期中提取出月份部分,用來匹配該日期的Regex可以五所示: |
| 新出現的“\s”符號是空白符號,匹配所有的空白字元,包括Tab字元。如果字串正確匹配,接下來如何提取出月份部分呢?只需在月份周圍加上一個圓括弧建立一個組,然後用ORO API(本文後面詳細討論)提取出它的值。修改後的Regex六所示: |
圖六:匹配所有Month DD,YYYY格式的日期,定義月份值為第一個組 |
| 為簡便起見,你可以使用一些為常見Regex建立的快捷符號。如表二所示: |
| 例如,在前面社會安全號碼的例子中,所有出現“[0-9]”的地方我們都可以使用“\d”。修改後的Regex七所示: |
圖七:匹配所有123-12-1234格式的社會安全號碼 |
| 有許多原始碼開放的Regex庫可供Java程式員使用,而且它們中的許多支援Perl 5相容的Regex文法。我在這裡選用的是Jakarta-ORORegex庫,它是最全面的RegexAPI之一,而且它與Perl 5Regex完全相容。另外,它也是最佳化得最好的API之一。 |
| Jakarta-ORO庫以前叫做OROMatcher,Daniel Savarese大方地把它贈送給了Jakarta Project。你可以按照本文最後參考資源的說明下載它。 |
| 我首先將簡要介紹使用Jakarta-ORO庫時你必須建立和訪問的對象,然後介紹如何使用Jakarta-ORO API。 |
| 首先,建立一個Perl5Compiler類的執行個體,並把它賦值給PatternCompiler介面對象。Perl5Compiler是PatternCompiler介面的一個實現,允許你把Regex編譯成用來匹配的Pattern對象。 |
| 要把Regex編譯成Pattern對象,調用compiler對象的compile()方法,並在調用參數中指定Regex。例如,你可以按照下面這種方式編譯Regex“t[aeio]n”: |
| 預設情況下,編譯器建立一個大小寫敏感的模式(pattern)。因此,上面代碼編譯得到的模式只匹配“tin”、“tan”、 “ten”和“ton”,但不匹配“Tin”和“taN”。要建立一個大小寫不敏感的模式,你應該在調用編譯器的時候指定一個額外的參數: |
| 建立好Pattern對象之後,你就可以通過PatternMatcher類用該Pattern對象進行模式比對。 |
| PatternMatcher對象根據Pattern對象和字串進行匹配檢查。你要執行個體化一個Perl5Matcher類並把結果賦值給PatternMatcher介面。Perl5Matcher類是PatternMatcher介面的一個實現,它根據Perl 5Regex文法進行模式比對: |
| 使用PatternMatcher對象,你可以用多個方法進行匹配操作,這些方法的第一個參數都是需要根據Regex進行匹配的字串: |
| · boolean matches(String input, Pattern pattern):當輸入字串和Regex要精確匹配時使用。換句話說,Regex必須完整地描述輸入字串。 |
| · boolean matchesPrefix(String input, Pattern pattern):當Regex匹配輸入字串起始部分時使用。 |
| · boolean contains(String input, Pattern pattern):當Regex要匹配輸入字串的一部分時使用(即,它必須是一個子串)。 |
| 另外,在上面三個方法調用中,你還可以用PatternMatcherInput對象作為參數替代String對象;這時,你可以從字串中最後一次匹配的位置開始繼續進行匹配。當字串可能有多個子串匹配給定的Regex時,用PatternMatcherInput對象作為參數就很有用了。用PatternMatcherInput對象作為參數替代String時,上述三個方法的文法如下: |
| · boolean matches(PatternMatcherInput input, Pattern pattern) |
| · boolean matchesPrefix(PatternMatcherInput input, Pattern pattern) |
| · boolean contains(PatternMatcherInput input, Pattern pattern) |
| 下面我們來看看Jakarta-ORO庫的一些應用執行個體。 |
| 任務:分析一個Web伺服器記錄檔,確定每一個使用者花在網站上的時間。在典型的BEA WebLogic記錄檔中,日誌記錄的格式如下: |
| 分析這個日誌記錄,可以發現,要從這個記錄檔提取的內容有兩項:IP地址和頁面訪問時間。你可以用分組符號(圓括弧)從日誌記錄提取出IP地址和時間標記。 |
| 首先我們來看看IP地址。IP地址有4個位元組構成,每一個位元組的值在0到255之間,各個位元組通過一個句點分隔。因此,IP地址中的每一個位元組有至少一個、最多三個數字。圖八顯示了為IP地址編寫的Regex: |
| IP地址中的句點字元必須進行轉義處理(前面加上“\”),因為IP地址中的句點具有它本來的含義,而不是採用Regex文法中的特殊含義。句點在Regex中的特殊含義本文前面已經介紹。 |
| 日誌記錄的時間部分由一對方括弧包圍。你可以按照如下思路提取出方括弧裡面的所有內容:首先搜尋起始方括弧字元(“[”),提取出所有不超過結束方括弧字元(“]”)的內容,向前尋找直至找到結束方括弧字元。圖九顯示了這部分的Regex。 |
| 現在,把上述兩個Regex加上分組符號(圓括弧)後合并成單個運算式,這樣就可以從日誌記錄提取出IP地址和時間。注意,為了匹配“- -”(但不提取它),Regex中間加入了“\s-\s-\s”。完整的Regex十所示。 |
| 現在Regex已經編寫完畢,接下來可以編寫使用Regex庫的Java代碼了。 |
| 為使用Jakarta-ORO庫,首先建立Regex字串和待分析的日誌記錄字串: |
| 這裡使用的Regex與圖十的Regex差不多完全相同,但有一點例外:在Java中,你必須對每一個向前的斜杠(“\”)進行轉義處理。圖十不是Java的表示形式,所以我們要在每個“\”前面加上一個“\”以免出現編譯錯誤。遺憾的是,轉義處理過程很容易出現錯誤,所以應該小心謹慎。你可以首先輸入未經轉義處理的Regex,然後從左至右依次把每一個“\”替換成“\\”。如果要複檢,你可以試著把它輸出到螢幕上。 |
| 初始化字串之後,執行個體化PatternCompiler對象,用PatternCompiler編譯Regex建立一個Pattern對象: |
| 現在,建立PatternMatcher對象,調用PatternMatcher介面的contain()方法檢查匹配情況: |
| 接下來,利用PatternMatcher介面返回的MatchResult對象,輸出匹配的組。由於logEntry字串包含匹配的內容,你可以看到類如下面的輸出: |
| 下面一個任務是分析HTML頁面內FONT標記的所有屬性。HTML頁面內典型的FONT標記如下所示: |
| 程式將按照如下形式,輸出每一個FONT標記的屬性: |
| 在這種情況下,我建議你使用兩個Regex。第一個十一所示,它從字型標記提取出“"face="Arial, Serif" size="+2" color="red"”。 |
| 第二個Regex十二所示,它把各個屬性分割成名字-值對。 |
| 現在我們來看看完成這個任務的Java代碼。首先建立兩個Regex字串,用Perl5Compiler把它們編譯成 Pattern對象。編譯Regex的時候,指定Perl5Compiler.CASE_INSENSITIVE_MASK選項,使得匹配操作不區分大小寫。 |
| 接下來,建立一個執行匹配操作的Perl5Matcher對象。 |
| 假設有一個String類型的變數html,它代表了HTML檔案中的一行內容。如果html字串包含FONT標記,匹配器將返回true。此時,你可以用匹配器對象返回的MatchResult對象獲得第一個組,它包含了FONT的所有屬性: |
| 接下來建立一個PatternMatcherInput對象。這個對象允許你從最後一次匹配的位置開始繼續進行匹配操作,因此,它很適合於提取FONT標記內屬性的名字-值對。建立PatternMatcherInput對象,以參數形式傳入待匹配的字串。然後,用匹配器執行個體提取出每一個FONT的屬性。這通過指定PatternMatcherInput對象(而不是字串對象)為參數,反覆地調用 PatternMatcher對象的contains()方法完成。PatternMatcherInput對象之中的每一次迭代將把它內部的指標向前移動,下一次檢測將從前一次匹配位置的後面開始。 |
| 下面我們來看看另一個處理HTML的例子。這一次,我們假定Web伺服器從widgets.acme.com移到了newserver.acme.com。現在你要修改一些頁面中的連結: |
| 如果能夠匹配這個Regex,你可以用下面的內容替換圖十三的連結: |
| 注意#字元的後面加上了$1。PerlRegex文法用$1、$2等表示已經匹配且提取出來的組。圖十三的運算式把所有作為一個組匹配和提取出來的內容附加到連結的後面。 |
| 現在,返回Java。就象前面我們所做的那樣,你必須建立測試字串,建立把Regex編譯到Pattern對象所必需的對象,以及建立一個PatternMatcher對象: |
| 接下來,用com.oroinc.text.regex包Util類的substitute()靜態方法進行替換,輸出結果字串: |
| Util.substitute()方法的文法如下: |
| 這個調用的前兩個參數是以前建立的PatternMatcher和Pattern對象。第三個參數是一個 Substiution對象,它決定了替換操作如何進行。本例使用的是Perl5Substitution對象,它能夠進行Perl5風格的替換。第四個參數是想要進行替換操作的字串,最後一個參數允許指定是否替換模式的所有匹配子串(Util.SUBSTITUTE_ALL),或只替換指定的次數。 |
| 【結束語】在這篇文章中,我為你介紹了Regex的強大功能。只要正確運用,Regex能夠在字串提取和文本修改中起到很大的作用。另外,我還介紹了如何在Java程式中通過Jakarta-ORO庫利用Regex。至於最終採用老式的字串處理方式(使用StringTokenizer,charAt,和substring),還是採用Regex,這就有待你自己決定了。 |