文本處理經常涉及的根據一個pattern的匹配。盡管java的character和assorted 的String類提供了low-level的pattern-matching支持,這種支持一般帶來了復雜的代碼。為了幫助你書寫簡單的pattern-matching代碼,java提供了regular expression。在介紹給你術語和java.util.regex包之后,Jeff Friesen explores 了許多那個包的Pattern類支持的正則表達式結構。然后他examines 了Pattern的方法和附加的java.util.regex 類。作為結束,他提供了一個正則表達式的實踐應用。
為察看術語列表,提示與警告,新的homework,上個月homework的回答,這篇文章的相關材料,請訪問study guide. (6,000 words; February 7, 2003)
By Jeff Friesen ,Translated By humx
文本處理經常的要求依據特定pattern匹配的代碼。它能讓文本檢索,email header驗證,從普通文本的自定義文本的創建(例如,用"Dear Mr. Smith" 替代 "Dear Customer"),等等成為可能。Java通過character和assorted string類支持pattern matching。由于low-level的支持一般帶來了復雜的pattern-matching代碼,java同時提供了regular expression來簡代碼。
Regular expressions經常讓新手迷惑。然而, 這篇文章驅散了大部分混淆。在介紹了regular expression術語,java.util.regex 包中的類, 和一個regular expression constructs的示例程序之后, 我explore了許多Pattern類支持的regular expression constructs。我也examine了組成Pattern 和java.util.regex 包中其它類的方法。一個practical 的正則表達式的應用程序結束了我的討論。
Note
Regular expressions的漫長歷史開始于計算機科學理論領域自動控制原理和formal 語言理論。它的歷史延續到Unix和其它的操作系統,在那里正則表達式被經常用作在Unix和Unix-like的工具中:像awk(一個由其創作者,Aho, Weinberger, and Kernighan,命名,能夠進行文本分析處理的編程語言), emacs (一個開發工具),和grep (一個在一個或多個文件中匹配正則表達式,為了全局地正則表達式打印的工具。
什么是正則表達式?
A regular expression,也被known as regex or regexp,是一個描述了一個字符串集合的pattern(template)。這個pattern決定了什么樣的字符串屬于這個集合,它由文本字符和元字符(metacharacters,由有特殊的而不是字符含義的字符)組成。為了識別匹配的檢索文本的過程—字符串滿足一個正則表達式—稱作模式匹配(pattern matching)。
Java''s java.util.regex 包通過Pattern,Matcher類和PatternSyntaxException異常支持pattern matching:
Pattern 對象,被known as patterns,是編譯的正則表達式。
Matcher 對象,或者matchers,在,實現了java.lang.CharSequence接口并作為文本source的字符序列中定位解釋matchers的引擎。
PatternSyntaxException 對象描述非法的regex patterns。
Listing 1 介紹這些類:
Listing 1. RegexDemo.java
// RegexDemo.java
import java.util.regex.*;
class RegexDemo {
public static void main (String [] args) {
if (args.length != 2)
System.err.println ("java RegexDemo regex text");
return;
}
Pattern p;
try {
p = Pattern.compile (args [0]);
}
catch (PatternSyntaxException e) {
System.err.println ("Regex syntax error: " + e.getMessage ());
System.err.println ("Error description: " + e.getDescription ());
System.err.println ("Error index: " + e.getIndex ());
System.err.println ("Erroneous pattern: " + e.getPattern ());
return;
}
String s = cvtLineTerminators (args [1]);
Matcher m = p.matcher (s);
System.out.println ("Regex = " + args [0]);
System.out.println ("Text = " + s);
System.out.println ();
while (m.find ()) {
System.out.println ("Found " + m.group ());
System.out.println (" starting at index " + m.start () +
" and ending at index " + m.end ());
System.out.println ();
}
}
// Convert \n and \r character sequences to their single character
// equivalents
static String cvtLineTerminators (String s) {
StringBuffer sb = new StringBuffer (80);
int oldindex = 0, newindex;
while ((newindex = s.indexOf ("\\n", oldindex)) != -1) {
sb.append (s.substring (oldindex, newindex));
oldindex = newindex + 2;
sb.append (''\n'');
}
sb.append (s.substring (oldindex));
s = sb.toString ();
sb = new StringBuffer (80);
oldindex = 0;
while ((newindex = s.indexOf ("\\r", oldindex)) != -1) {
sb.append (s.substring (oldindex, newindex));
oldindex = newindex + 2;
sb.append (''\r'');
}
sb.append (s.substring (oldindex));
return sb.toString ();
}
}
RegexDemo''s public static void main(String [] args) 方法validates 兩個命令行參數:一個指出正則表達式,另外一個指出文本。在創建一個pattern之后,這個方法轉換所有的文本參數,new-line and carriage-return line-terminator 字符序列為它們的實際meanings 。例如,一個new-line字符序列(由反斜杠后跟n表示)轉換成一個new-line字符(用數字表示為10)。在輸出了regex和被轉換的命令行文本參數之后,main(String [] args) 方法從pattern創建了一個matcher,它隨后查找了所有的matches 。對于每一個match,它所出現的字符和信息的位置被輸出。
為了完成模式匹配,RegexDemo 調用了java.util.regex包中類的不同的方法。不要使你自己現在就理解這些方法;我們將在后邊的文章探討它們。更重要的是,編譯 Listing 1: 你需要RegexDemo.class來探索Pattern''s regex 結構。
探索Pattern''s regex 構造
Pattern''s SDK 文檔提供了一部分正則表達式結構的文檔。除非你是一個avid正則表達式使用者,一個最初的那段文檔的閱讀會讓你迷惑。什么是quantifiers,greedy之間的不同是什么, reluctant, 和 possessive quantifiers? 什么是 character classes, boundary matchers, back references, 和 embedded flag expressions? 為了回答這些和其它的問題,我們探索了許多Patter認可的regex constructs或 regex pattern 種類。我們從最簡單的regex construct 開始:literal strings。
Caution
不要認為Pattern和Perl5的正則表達式結構是一樣的。盡管他們有很多相同點,他們也有許多,它們支持的metacharacters結構的不同點。 (更多信息,察看在你的平臺上的你的SDK Pattern類的文檔。)
Literal strings
當你在字處理軟件的檢索對話框輸入一個你指定一個literal string 的時候,你就指定了一個regex expression construct 。執行以下的RegexDemo 命令行來察看一下這個regex construct 的動作:
java RegexDemo apple applet
上邊的這個命令行確定了apple 作為一個包含了字符a, p, p, l, and e(依次)的字符regex construct。 這個命令行同時也確定了applet 作為pattern-matching的文本。執行命令行以后,看到以下輸出:
Regex = apple
Text = applet
Found apple
starting at index 0 and ending at index 5
輸出的regex 和text 命令行,預示著在applet中一個applet的成功的匹配,并表示了匹配的開始和結束的索引:分別為0和5。開始索引指出了一個pattern match出現的第一個文本的開始位置,結束索引指明了這個match后的第一個text的位置。換句話說,匹配的text的范圍包含在開始索引和去掉結束索引之間(不包含結束索引)。
Metacharacters
盡管string regex constructs 是有用的,更強大的regex contsruct聯合了文本字符和元字符。例如,在a.b,這個句點metacharacter (.) 代表在a個b之間出現的任何字符。 為了察看元字符的動作, 執行以下命令行:
java RegexDemo .ox "The quick brown fox jumps over the lazy ox."
以上命令指出.ox 作為regex ,和The quick brown fox jumps over the lazy ox.作為文本源text。RegexDemo 檢索text來匹配以任意字符開始以ox結束的match,并產生如下輸出:
Regex = .ox
Text = The quick brown fox jumps over the lazy ox.
Found fox
starting at index 16 and ending at index 19
Found ox
starting at index 39 and ending at index 42
這個輸出展示了兩個matches:fox和ox。. metacharacter 在第一個match中匹配f ,在第二個match中匹配空格。
假如我們用前述的metacharacter 替換.ox會怎么樣呢?也就是,我們指定java RegexDemo . "The quick brown fox jumps over the lazy ox."會有什么樣的輸出,因為period metacharacter 匹配任何字符, RegexDemo 在命令行輸出每一個匹配字符,包括結尾的period字符。
Tip
為了指定.或者任何的元字符作為在一個regex construct 作為literal character,引用—轉換meta狀態到literal status—用以下兩種方法之一:
在元字符之前放置反斜杠。
將元字符放在\Q和\E之間(例如:\Q.\E)。
在每種情形下,不要忘記在string literal(例如:String regex = \\.;
)中出現時(像 \\. or \\Q.\\E)的雙倍的反斜杠。不要在當它在命令行參數中出現的時候用雙倍的反斜杠。
Character classes
有時我們限定產生的matches到一個特定的字符集和。例如,我們可以檢索元音a, e, i, o, and u ,任何一個元音字符的出現都以為著一個match。A character類, 通過在方括號之間的一個字符集和指定的regex construct ,幫我們完成這個任務。Pattern 支持以下的character classes:
簡單字符: 支持被依次放置的字符串并僅匹配這些字符。例如:[abc] 匹配字符a, b, and c。以下的命令行提供了另外一個示例:
java RegexDemo [csw] cave
java RegexDemo [csw] cave [csw]中c匹配在cave中的c。沒有其它的匹配存在。
否定: 以^ metacharacter 元字符開始且僅匹配沒有在class中出現的字符。例如:[^abc]匹配所有除了a, b, 和c以外的字符,以下的命令行提供了另外一個示例:
java RegexDemo [^csw] cave
java RegexDemo [^csw] cave 匹配在cave中遇到的a, v, 和e。沒有其它的匹配存在。
范圍: 包含在元字符(-)左側的字符開始,元字符(-)右側字符結束的所有字符。僅匹配在范圍內的字符。例如: [a-z] 匹配所有的小寫字母。以下的命令行提供了另外一個示例:
java RegexDemo [a-c] clown
java RegexDemo [a-c] clown 匹配在clown中的c。沒有其它的匹配存在。
Tip
通過將它們放置在一起來在一個range character class內聯合多個范圍。例如:[a-zA-Z] 匹配所有的大寫和小寫字母。
聯合: 由多個嵌套的character classes組成,匹配屬于聯合結果的所有字符。例如:[a-d[m-p]] 匹配字符a到d和m到p。 characters a through d and m through p。以下的命令行提供了另外一個示例:
java RegexDemo [ab[c-e]] abcdef
java RegexDemo [ab[c-e]] abcdef 匹配它們在abcdef中的副本 a, b, c, d, and e。沒有其它的匹配存在。
交集: 由所有嵌套的class的共同部分組成,且僅匹配字符的共同部分。例如:[a-z&&[d-f]] 匹配字符d, e, 和 f。以下的命令行提供了另外一個示例:
java RegexDemo [aeiouy&&[y]] party
java RegexDemo [aeiouy&&[y]] party 匹配在party中的y。沒有其它的匹配存在。
差集: 由除了那些在否定嵌套character class中的字符外所有保留的字符組成。例如:[a-z&&[^m-p]] 匹配 字符a到l和q到z。以下的命令行提供了另外一個示例:
java RegexDemo [a-f&&[^a-c]&&[^e]] abcdefg
java RegexDemo [a-f&&[^a-c]&&[^e]] abcdefg 匹配在abcdefg中的 d 和f。沒有其它的匹配存在。
預定義的character classes
一些在regexes 中出現足夠次數的character classes 提供了shortcuts。Pattern用預定義的character class提供了這樣的shortcuts,如Table 1所示。使用預定義的character classes來簡化你的regexes和最小化regex語法錯誤。
Table 1. 預定義的character classes
Predefined character class
Description
\d
A 數字。 相當于[0-9]。
\D
A 非數字。相當于[^0-9]。
\s
A whitespace character。相當于[ \t\n\x0B\f\r]。
\S
A 非空格字符。相當于[^\s]。
\w
A 一個字符。相當于[a-zA-Z_0-9]。
\W
A 非字符,相當于[^\w]。
隨后的命令行示例使用了\w預定義character class來identify 命令行中的所有word characters。
java RegexDemo \w "aZ.8 _"
上邊的命令行產生了以下的輸出,它展示了句點和space characters 不被考慮為word character:
Regex = \w
Text = aZ.8 _
Found a
starting at index 0 and ending at index 1
Found Z
starting at index 1 and ending at index 2
Found 8
starting at index 3 and ending at index 4
Found _
starting at index 5 and ending at index 6
Note
Pattern的SDK文檔引用句點元字符作為匹配除了line terminator,一個或者兩個標志一行結束的預定義的標志之外的任何字符,除非 dotall mode (隨后討論)有效。Pattern 識別以下line terminators:
The 回車符 (\r\)
The 回行符 (\n)
The 回車符緊跟回行符 (\r\n)
The 回行字符 (\u0085)
The 行分割字符 (\u2028)
The 段落分割字符 (\u2029)
捕獲組
Pattern支持在pattern匹配的過程中,一個regex construct 調用capturing group 來保存為了以后recall 的match的字符;此結構是由圓括號包圍的字符序列。在捕獲的group中的所有字符在匹配的過程中被作為一個單獨的單元。例如,(Java) capturing group 結合了字符 J, a, v, 和a為一個單獨單元。Capturing group依據在text中Java的出現來匹配Java pattern。每一個match用下一個匹配的Java字符替代了前一個match保存的Java字符。
Capturing groups 在其它capturing groups內被嵌套。例如:在(Java( language)),( language) 在(Java)內嵌套。每一個嵌套或非嵌套的capturing group有它自己的數字,數字從1開始,Capturing 數字從左邊至右。在這個例子中,(Java( language))是capturing group 1,( language)是capturing group 2。在(a)(b),(a)是捕獲組1,(b)是捕獲組2。
每一個capturing group隨后通過a back reference來recall保存的match。指定跟隨在一個反斜杠后的數字來指示一個capturing group,back reference來 recalls 一個capturing group捕獲的文本字符。一個back reference 的出現導致了一個matcher 使用the back reference的 capturing group number來recall捕獲組保存的match ,然后使用匹配的字符進行進一步的匹配操作。隨后的示例示范了為了檢查語法錯誤進行的text 搜索的用法:
java RegexDemo "(Java( language)\2)" "The Java language language"
這個例子使用(Java( language)\2) regex為了檢查語法錯誤,來檢索字符串The Java language language,那里Java直接地在兩個連續出現的language之前。Regex 指定了兩個capturing groups: number 1 is (Java( language)\2), 它匹配Java language language,number 2 is ( language), 它匹配由language跟隨的space characer。\2 back reference recalls number 2''s 保存的match,它允許matcher檢索空格后跟隨language的第二次出現,which 直接地跟隨space character and language的第一次出現。隨后的輸出顯示了RegexDemo''s matcher 找到了什么:
Regex = (Java( language)\2)
Text = The Java language language
Found Java language language
starting at index 4 and ending at index 26
量詞
Quantifiers大概是理解起來最讓人迷惑的regex 結構。一部分混淆來自于盡力的理解18個量詞邏輯(六個基本邏輯被組織為三個主要邏輯)。其它的一個混淆來自于費盡的0長度匹配的理解。一旦你理解了這個概念和18 categories, 大部分(假如不是全部)混淆將消失。
Note
簡要地, 著一部分主要討論18 個quantifier categories 和zero-length 匹配的概念。為了更詳盡的討論和更多示例,請學習The Java Tutoria 的"Quantifiers"部分。
一個quantifier 是一個隱式或顯示的為一個pattern綁定一個數量值的正則表達式結構。這個數字值解決定了匹配一個pattern的次數。Pattern的六個基本的quantifiers匹配一個pattern一次或者根本不匹配,0次或者多次,一次或者多次,一個精確的數字次數,至少x次和 至少x次但不超過y次。
六個基本的quantifier categories 在每一個三個主要的類別:greedy, reluctant和 possessive中復制。Greedy quantifiers 嘗試找到最長的匹配。與之對照,reluctant quantifiers 嘗試找到最短的匹配。Possessive quantifiers 也嘗試找到最長的匹配。然而,他們和greedy quantifies在工作方式上不同。盡管greedy 和possessive quantifiers 迫使一個matcher 在進行第一次匹配之前讀取整個的text,greedy quantifiers 常常導致為了找到一個match進行多次嘗試,然而possessive quantifiers 讓一個matcher 僅嘗試一個match一次。
隨后的示例描述了六種基本的quantifiers 在greedy category類別下,單個fundamental quantifier 在每一個 reluctant 和 possessive categories類別下的行為。這些示例也描述了0匹配的概念:
1. java RegexDemo a? abaa: 使用一個greedy quantifier 來在abaa中匹配a 一次或者根本不匹配。以下是輸出結果:
Regex = a?
Text = abaa
Found a
starting at index 0 and ending at index 1
Found
starting at index 1 and ending at index 1
Found a
starting at index 2 and ending at index 3
Found a
starting at index 3 and ending at index 4
Found
starting at index 4 and ending at index 4
這個輸出展示了五次匹配。盡管第一、三和四次匹配的出現展示了三次匹配中位置并不奇怪,第一、第五次的匹配大概有點奇怪。這個匹配好像指出a匹配b和文本的結束。然而,不是這種情況。a?并不查找b和文本結尾。相反, 它查找a的出現或者缺失。當a? 查找a失敗的時候,它以零長度的匹配返回那個事實(a缺失),在零長度那里起始和結束位置的索引相同。Zero-length matches 發生在空文本, 最后一個文本字符之后,或者任何量個字符之間。
2. java RegexDemo a* abaa: 使用一個greedy quantifier在abaa 中匹配a零次或多次。以下是輸出結果:
Regex = a*
Text = abaa
Found a
starting at index 0 and ending at index 1
Found
starting at index 1 and ending at index 1
Found aa
starting at index 2 and ending at index 4
Found
starting at index 4 and ending at index 4
輸出展示了四次匹配。像使用 a?,a* 產生了zero-length 匹配。第三個匹配,a* 匹配了aa, 很有趣。不像 a?,a* 匹配一個或者多個連續的a。
3. java RegexDemo a+ abaa: 使用一個greedy quantifier在abaa 中匹配a一次或多次。以下是輸出結果:
Regex = a+
Text = abaa
Found a
starting at index 0 and ending at index 1
Found aa
starting at index 2 and ending at index 4
輸出展示了兩個匹配 。不像 a? 和 a*,a+ 沒有匹配a的卻失。因而,沒有零長度匹配產生。像 a*,a+匹配了連續的a。
4. java RegexDemo a{2} aababbaaaab: 使用greedy quantifier 來匹配中的每一個 aababbaaaab中的 aa序列。以下是輸出結果:
Regex = a{2}
Text = aababbaaaab
Found aa
starting at index 0 and ending at index 2
Found aa
starting at index 6 and ending at index 8
Found aa
starting at index 8 and ending at index 10
5. java RegexDemo a{2,} aababbaaaab: 使用了greedy quantifier 來匹配在ababbaaaab中兩個或更多的匹配,以下是輸出結果:
Regex = a{2,}
Text = aababbaaaab
Found aa
starting at index 0 and ending at index 2
Found aaaa
starting at index 6 and ending at index 10
6. java RegexDemo a{1,3} aababbaaaab: 使用greedy quantifier 來匹配在aababbaaaab中出現的a、aa或者aaa。以下是輸出結果:
Regex = a{1,3}
Text = aababbaaaab
Found aa
starting at index 0 and ending at index 2
Found a
starting at index 3 and ending at index 4
Found aaa
starting at index 6 and ending at index 9
Found a
starting at index 9 and ending at index 10
7. java RegexDemo a+? abaa: 使用一個reluctant quantifier 在abaa中一次或多次匹配a。以下是輸出結果:
Regex = a+?
Text = abaa
Found a
starting at index 0 and ending at index 1
Found a
starting at index 2 and ending at index 3
Found a
starting at index 3 and ending at index 4
不像在第三個例中的greedy變量,reluctant 示例產生了三個單獨的匹配,因為reluctant quantifier盡力的查找最短的匹配。
8. java RegexDemo .*+end "This is the end": 使用了possessive quantifier 來匹配在this is the end中的以end結尾的任意字符0次或者多次。以下是輸出結果:
Regex = .*+end
Text = This is the end
由于這個possessive quantifier consume了整個文本,沒有留下任何東西來匹配end,它沒有產生匹配。相比之下,在java RegexDemo .*end "This is the end" 的greedy quantifier,因為它每次backing off一個字符直到最右端的end匹配,產生了一個匹配。(這個quantifier與greedy的不同就在后者的匹配過程中一旦匹配的字符,在隨后的匹配中就不再使用。因此.*這部分正則表達式就匹配了全部的字符串,就沒有字符可以與end匹配了。)
Boundary matchers
我們有時想在一行的開始、在單詞的邊界、文本的結束等等匹配pattern。使用 boundary matcher,一個指定了匹配邊界的正則表達式結構,完成這個任務。Table 2 表示了Pattern的邊界匹配支持。
Table 2. Boundary matchers
Boundary Matcher
Description
^
一行的開始
$
一行的結束
\b
單詞的邊界
\B
非單詞邊界
\A
文本的開始
\G
前一個匹配的結束
\Z
The end of the text (but for the final line terminator, if any)
\z
文本結束
下邊的命令行示例使用了^ 邊界匹配元字符 ensure 由零個或者多個字符跟隨的行開始。
java RegexDemo ^The\w* Therefore
^ 指出了前三個字符必須匹配pattern后的T、h和e字符。可跟隨任意數量的字符。以上的命令行產生以下輸出:
Regex = ^The\w*
Text = Therefore
Found Therefore
starting at index 0 and ending at index 9
把命令行改為java RegexDemo ^The\w* " Therefore"。發生了什么事?因為在therefore前的一個空格,沒有匹配被發現。
Embedded flag expressions
Matcher假設了確定的卻省值,例如大小寫敏感的匹配。一個程序可以使用an embedded flag expression 來覆蓋缺省值,也就是,使用一個正則表達式結構,圓括號元字符包圍一個問號元字符后跟小寫字母。Pattern認可以下的embedded flag expressions :
(?i): enables 大小寫不敏感的pattern 匹配。例如:java RegexDemo (?i)tree Treehouse 來匹配tree和Tree。大小寫敏感是缺省值。
(?x): 允許空格和注釋用#元字符開始出現在Pattern中。一個matcher 忽略全部它們。例如:java RegexDemo ".at(?x)#match hat, cat, and so on" matter 匹配.at和mat。缺省地,空格和注釋式不被允許的;一個matcher 將它們考慮為對match有貢獻的字符。
(?s): 使dotall 方式有效。在這種模式中,句點除了其它字符外還匹配text結束。 例如:java RegexDemo (?s). \n ,. 匹配了 \n。Nondotall 方式是缺省的:不匹配行結尾。
(?m): 使多行方式有效。在多行模式下 ^ and $ 恰好分別的在一行的終結或末端之后或者之前。例如:java RegexDemo (?m)^.ake make\rlake\n\rtake 匹配 .ake 和 make、 lake與 take。非多行模式是缺省的: ^ and $ match僅在整個文本的開始和結束。
(?u): enables Unicode-aware case folding. This flag works with (?i) to perform case-insensitive matching in a manner consistent with the Unicode Standard. The default: case-insensitive matching that assumes only characters in the US-ASCII character set match。
(?d): enables Unix lines mode. In that mode, a matcher recognizes only the \n line terminator in the context of the ., ^, and $ metacharacters. Non-Unix lines mode is the default: a matcher recognizes all terminators in the context of the aforementioned metacharacters。
Embedded flag expressions 類似于 capturing groups因為兩個regex constructs都用圓括號包圍字符。不像capturing group,embedded flag expression 沒有捕獲匹配的字符。因而,一個embedded flag expression是noncapturing group的特例。也就是說,一個不捕獲text字符的regex construct ;它指定了由元字符圓括號包圍的字符序列。在Pattern''s SDK 文檔中出現了一些noncapturing groups。
Tip
為了在正則表達式中指定多個embedded flag 表達式?;蛘甙伤鼈儾⑴诺姆旁谝黄?(e.g., (?m)(?i)) 或者 把它們的小寫字母并排的放在一起 (e.g., (?mi))。
探索java.util.regex 類的方法
java.util.regex包的三個類提供了為幫我書寫更健壯的正則表達式代碼和創建強大的text處理工具許多的方法。 我們從Pattern類開始探索這些方法。
Note
你也可以explore CharSequence 接口的當你創建一個新的字符序列類要實現的方法。僅實現 CharSequence 接口的類是java.nio.CharBuffer, String, 和 StringBuffer。
Pattern 方法
除非代碼將一個string編譯為Pattern對象一個regex表達式式沒有用處的。用以下編輯方法中的一個完成這個任務:
public static Pattern compile(String regex): 將regex內容編譯為在一個新的Pattern對象內存儲的樹狀結構的對象表示。返回那個對象引用。例如:Pattern p = Pattern.compile ("(?m)^\\.");創建了一個,存儲了一個編譯的表示了匹配以句點開始的行的表示。
public static Pattern compile(String regex, int flags): 完成前一個方法的相同任務。然而,它同時考慮包含了flag常量(flags指定的)。Flag常量在Pattern中(except the canonical equivalence flag, CANON_EQ)被作為二選一的embedded flag expressions被聲明。例如:Pattern p = Pattern.compile ("^\\.", Pattern.MULTILINE);和上一個例子等價,Pattern.MULTILINE 常量和the (?m) embedded flag 表達式完成相同的任務。(參考SDK''s Pattern 文檔學習其它的常量。) 假如不是這些在Pattern中被定義的常量在flag中出現,方法將拋出IllegalArgumentException 異常。
假如需要,通過調用以下方法可以得到一個Pattern對象的flag和最初的被編譯為對象的正則表達式:
public int flags(): 返回當正則表達式編譯時指定的Pattern的flag。例如:System.out.println (p.flags ()); 輸出p引用的的Pattern相關的flag。
public String pattern(): 返回最初的被編譯進Pattern的正則表達式。例如:System.out.println (p.pattern ()); 輸出對應p引用Pattern的正則表達式。(Matcher 類包含了類似的返回Matcher相關的Pattern對象的Pattern pattern() 方法。)
在創建一個Pattern對象后,你一般的通過調用Pattern的公有方法matcher(CharSequence text)獲得一個Matcher對象。這個方法需要一個簡單的,實現了CharSequence接口的文本對象參數。獲得的對象在pattern匹配的過程中掃描輸入的文本對象。例如:Pattern p = Pattern.compile ("[^aeiouy]"); Matcher m = p.matcher ("This is a test."); 獲得一個在text中匹配所有非元音字母的matcher。
當你想檢查一個pattern是否完全的匹配一個文本序列的時候創建Pattern和Matcher對象是令人煩惱的。幸運的是,Pattern提供了一個方便的方法完成這個任務;public static boolean matches(String regex, CharSequence text)。當且僅當整個字符序列匹配regex的pattern的場合下靜態方法返回布爾值true。例如:System.out.println (Pattern.matches ("[a-z[\\s]]*", "all lowercase letters and whitespace only"));返回布爾值true, 指出僅空格字符和小寫字符在all lowercase letters and whitespace only中出現。
書寫代碼將text分成它的組成部分(例如雇員記錄文件到一個字段的set) 是許多開發者發現乏味的任務。Pattern 通過提供一對字符分割方法來減輕那種tedium。
public String [] split(CharSequence text, int limit): 分割在當前的Pattern對象的pattern匹配周圍的text。這個方法返回一個數組,它的每一個條目指定了一個從下一個由pattern匹配(或者文本結束)分開的字符序列;且所有條目以它們在text中出現相同的順序存儲。書組條目的數量依賴于limit,它同時也控制了匹配發生次數。一個正數意味著,至多,limit-1 個匹配被考慮且數組的長度不大于限定的條目數。一個負值以為著所有匹配的可能都被考慮且數組可以任意長。一個0值以為著所有可能匹配的條目都被考慮,數組可以有任意的長度,且尾部的空字符串被丟棄。
public String [] split(CharSequence text): 用0作為限制調用前邊方法,返回方法調用的結果。
假如你想一個拆分雇員記錄,包含了姓名,年齡,街道和工資,為它的組成部分。以下的代碼用split(CharSequence text)方法完成了這個任務:
Pattern p = Pattern.compile (",\\s");
String [] fields = p.split ("John Doe, 47, Hillsboro Road, 32000");
for (int i = 0; i < fields.length; i++)
System.out.println (fields [i]);
The code fragment above specifies a regex that matches a comma character immediately followed by a single-space character and produces the following output:
John Doe
47
Hillsboro Road
32000
Note
String合并了三個方便的方法調用它們等價的Pattern方法: public boolean matches(String regex), public String [] split(String regex), and public String [] split(String regex, int limit)。
Matcher 方法
Matcher對象支持不同類型的pattern匹配操作,例如掃描text查找下一個match;嘗試根據一個pattern來匹配整個文本;根據一個pattern嘗試匹配部分text。用以下的方法完成這些任務:
public boolean find(): 掃描text查找下一個match。此方法,或者在text的開始掃描,假如上一次的方法調用返回true且這個matcher沒有被reset,在前一個match后的第一個字符開始掃描。假如一個match被找到的話返回布爾值true。Listing 1 展示了一個例子。
public boolean find(int start): 重新安排matcher掃描下一個match。掃描從start指定的index開始。假如一個match被找到的話返回布爾值true。例如:m.find (1); 從index1開始掃描。(索引0被忽略。)假如start包含了一個負數或者一個超出了matcfher的text長度的值,這個方法拋出IndexOutOfBoundsException異常。
public boolean matches(): 嘗試根據pattern匹配整個text。在這個text匹配的情形下返回true。例如: Pattern p = Pattern.compile ("\\w*"); Matcher m = p.matcher ("abc!"); System.out.println (m.matches ()); 輸出false因為整個abc! text 包含了非字母word characters。
public boolean lookingAt(): 嘗試根據pattern匹配text。假如一個match被找到的話返回布爾值true。 不像 matches(), 整個text不需要被匹配。例如:Pattern p = Pattern.compile ("\\w*"); Matcher m = p.matcher ("abc!"); System.out.println (p.lookingAt ()); 輸出true因為text abc!的開始部分僅包含word 字符。
不像Pattern對象,Matcher 包含了狀態信息。有時,你在一個pattern 匹配后想reset一個matcher清除那些信息。下邊的方法reset了一個matcher:
public Matcher reset(): 重置了一個matcher的狀態,包括matcher的append position(被清除為0)。下一個pattern的匹配操作從matcher新文本的起始開始。返回當前的matcher對象引用。例如:m.reset (); 通過引用m重置了matcher。
public Matcher reset(CharSequence text): 重新設置一個matcher的狀態且設置了matcher的文本內同。下一個pattern的匹配操作在matcher新的文本的起始位置開始。返回當前的matcher對象引用。例如:m.reset ("new text"); 重置m引用的對象并制定 新的text作為matcher的新text。
一個matcher的append position 決定了matcher的text的追加到一個StringBuffer對象中的開始位置。以下的方法使用了append position:
public Matcher appendReplacement(StringBuffer sb, String replacement): 讀取matcher的text并將它們追加到sb引用的StringBuffer對象。這個方法在前一個pattern match的最后一個字符之后停止讀取。此method 然后添加replacement引用的中的characters 到StringBuffer 對象。(替換字符串可以包含上一個匹配捕獲的文本的引用,通過dollar-sign characters ($) 和 capturing group 數) 最終,這個方法設置了matcher的append position為最后一個匹配字符的位置加上1。一個當前的matcher對象的引用返回。假如這個matcher對象還沒有執行match或者上次的match嘗試失敗此方法將拋出一個IllegalStateException 異常。假如replacement指定了一個pattern中不存在的capturing group 一個IndexOutOfBoundsException異常將被拋出。
public StringBuffer appendTail(StringBuffer sb): 追加所有的text 到StringBuffer對象并返回對象引用。在最后一次調用appendReplacement(StringBuffer sb, String replacement) 方法之后,調用appendTail(StringBuffer sb) copy剩余的text到StringBuffer對象。
隨后的例子調用appendReplacement(StringBuffer sb, String replacement) 和 appendTail(StringBuffer sb) 方法來替換所有在one cat, two cats, or three cats on a fence 中出現的cat為caterpillar。一個capturing group 和 在replacement中的capturing group的引用允許在每一個cat匹配后插入erpillar:
Pattern p = Pattern.compile ("(cat)");
Matcher m = p.matcher ("one cat, two cats, or three cats on a fence");
StringBuffer sb = new StringBuffer ();
while (m.find ())
m.appendReplacement (sb, "$1erpillar");
m.appendTail (sb);
System.out.println (sb);
此示例產生如下輸出:
one caterpillar, two caterpillars, or three caterpillars on a fence
其它的兩個替換方法使用替換的文本替換第一個match和所有的match成為可能:
public String replaceFirst(String replacement): 重置matcher,創建一個新的String對象,拷貝所有匹配的文本字符(直到第一個match)到String,追加替換字符到String,拷貝剩余的字符到Strring,并返回對象引用。(替換字符串可以包含上一個匹配捕獲的文本的引用,通過dollar-sign characters ($) 和 capturing group 數。)
public String replaceAll(String replacement): 操作和上一個方法類似。然而,replaceAll(String replacement) 用替換字符替換所有匹配。
正則表達式\s+ 探測在文本中出現的一次或多次出現的空格。隨后的例子使用了這個regex 并調用了replaceAll(String replacement) 方法來從text刪除duplicate whitespace :
Pattern p = Pattern.compile ("\\s+");
Matcher m = p.matcher ("Remove the \t\t duplicate whitespace. ");
System.out.println (m.replaceAll (" "));
此示例產生如下輸出:
Remove the duplicate whitespace.
Listing 1 包含了System.out.println ("Found " + m.group ());. 注意方法調用group()。此方法是capturing group-oriented 的Matcher方法:
public int groupCount(): 返回在matcher的pattern中capturing groups 的個數。這個計數沒有包含特定的capturing group 數字 0,它捕獲前一個match(不管一個pattern包含capturing groups與否。)
public String group(): 通過capturing group 數字 0記錄返回上一個match的字符。此方法可以根據一個空的字符串返回一個空字符串。假如match還沒有被嘗試或者上次的match操作失敗將拋出IllegalStateException異常。
public String group(int group): 像上一個方法,除了通過group指定的capturing group number返回以前的match字符外。假如沒有group number 指定的capturing group在pattern中存在,此方法拋出 一個IndexOutOfBoundsException 異常。
以下代碼示范了the capturing group 方法:
Pattern p = Pattern.compile ("(.(.(.)))");
Matcher m = p.matcher ("abc");
m.find ();
System.out.println (m.groupCount ());
for (int i = 0; i <= m.groupCount (); i++)
System.out.println (i + ": " + m.group (i));
The example produces the following output:
3
0: abc
1: abc
2: bc
3: c
Capturing group 數字0 保存了previous match 且與has nothing to do with whether 一個capturing group在一個pattern中出現與否沒有任何關系。也就是 is (.(.(.)))。其它的三個capturing groups捕獲了previous match屬于這個capturing groups的字符。例如,number 2, (.(.)), 捕獲 bc; and number 3, (.), 捕獲 c.
在我們離開討論Matcher的方法之前,我們將examine四個match位置方法:
public int start(): 返回previous match的開始位置。假如match還沒有被執行或者上次的match失敗,此方法拋出一個IllegalStateException異常。
public int start(int group): 類似上一個方法,除了返回group指定的capturing group 的相關的previous match 的開始索引外,假如在pattern中沒有指定的capturing group number 存在,start(int group) 拋出IndexOutOfBoundsException 異常。
public int end(): 返回上次match中匹配的字符的索引位置加上1。假如match還沒有被嘗試或者上次的match操作失敗將拋出IllegalStateException異常。
public int end(int group): 類似上一個方法,除了返回group指定的capturing group 的相關的previous match 的end索引外。假如在pattern中沒有指定的capturing group number 存在,end(int group) 拋出IndexOutOfBoundsException 異常。
下邊的示例示范了兩個match position 方法,為capturing group number 2報告起始/結束match 位置:
Pattern p = Pattern.compile ("(.(.(.)))");
Matcher m = p.matcher ("abcabcabc");
while (m.find ())
{
System.out.println ("Found " + m.group (2));
System.out.println (" starting at index " + m.start (2) +
" and ending at index " + m.end (2));
System.out.println ();
}
The example produces the following output:
Found bc
starting at index 1 and ending at index 3
Found bc
starting at index 4 and ending at index 6
Found bc
starting at index 7 and ending at index 9
輸出show我們僅僅對與capturing group number 2相關的matcher感興趣,也就是這些匹配的起始結束位置。
Note
String 引入了兩個方便的和調用Matcher等價的方法:public String replaceFirst(String regex, String replacement) 和 public String replaceAll(String regex, String replacement)。
PatternSyntaxException methods
Pattern的方法當它們發現非法的正則表達式語法錯誤的時候拋出PatternSyntaxException 異常。一個異常處理器可以調用PatternSyntaxException 的方法來獲得拋出的關于語法錯誤的PatternSyntaxException 對象的信息。
public String getDescription(): 返回語法錯誤描述。
public int getIndex(): 返回語法錯誤發生位置的近似索引或-1,假如index是未知的。
public String getMessage(): 建立一個多行的,包含了其它三個方法返回的信息的綜合,以可視的方式指出在pattern中錯誤的位置字符串。
public String getPattern(): 返回不正確的正則表達式。
因為PatternSyntaxException 從java.lang.RuntimeException繼承而來,代碼不需要指定錯誤handler。This proves appropriate when regexes are known to have correct patterns。但當有潛在的pattern語法錯誤存在的時候,一個異常handler是必需的。因而,RegexDemo的源代碼(參看 Listing 1) 包含了try { ... } catch (ParseSyntaxException e) { ... },它們調用了PatternSyntaxException四個異常方法中的每一個來獲得非法pattern的信息。
什么組成了非法的pattern?在embedded flag expression 中沒有指定結束的元字符結束符號就是一個例。假如你執行java RegexDemo (?itree Treehouse。此命令的非法正則表達式(?tree pattern 導致 p = Pattern.compile (args [0]); 拋出PatternSyntaxException 異常。你將看到如下輸出:
Regex syntax error: Unknown inline modifier near index 3
(?itree
^
Error description: Unknown inline modifier
Error index: 3
Erroneous pattern: (?itree
Note
public PatternSyntaxException(String desc, String regex, int index) 構造函數讓你創建你自己的PatternSyntaxException對象, That constructor comes in handy should you ever create your own preprocessing compilation method that recognizes your own pattern syntax, translates that syntax to syntax recognized by Pattern''s compilation methods, and calls one of those compilation methods. If your method''s caller violates your custom pattern syntax, you can throw an appropriate PatternSyntaxException object from that method。
一個正則表達式應用實踐
Regexes let you create powerful text-processing applications. One application you might find helpful extracts comments from a Java, C, or C++ source file, and records those comments in another file. Listing 2 presents that application''s source code:
Listing 2. ExtCmnt.java
// ExtCmnt.java
import java.io.*;
import java.util.regex.*;
class ExtCmnt
{
public static void main (String [] args)
{
if (args.length != 2)
{
System.err.println ("usage: java ExtCmnt infile outfile");
return;
}
Pattern p;
try
{
// The following pattern lets this extract multiline comments that
// appear on a single line (e.g., /* same line */) and single-line
// comments (e.g., // some line). Furthermore, the comment may
// appear anywhere on the line.
p = Pattern.compile (".*/\\*.*\\*/|.*//.*$");
}
catch (PatternSyntaxException e)
{
System.err.println ("Regex syntax error: " + e.getMessage ());
System.err.println ("Error description: " + e.getDescription ());
System.err.println ("Error index: " + e.getIndex ());
System.err.println ("Erroneous pattern: " + e.getPattern ());
return;
}
BufferedReader br = null;
BufferedWriter bw = null;
try
{
FileReader fr = new FileReader (args [0]);
br = new BufferedReader (fr);
FileWriter fw = new FileWriter (args [1]);
bw = new BufferedWriter (fw);
Matcher m = p.matcher ("");
String line;
while ((line = br.readLine ()) != null)
{
m.reset (line);
if (m.matches ()) /* entire line must match */
{
bw.write (line);
bw.newLine ();
}
}
}
catch (IOException e)
{
System.err.println (e.getMessage ());
return;
}
finally // Close file.
{
try
{
if (br != null)
br.close ();
if (bw != null)
bw.close ();
}
catch (IOException e)
{
}
}
}
}
在創建Pattern 和Matcher 對象之后,ExtCmnt 逐行的讀取一個文本文件的內容。對于每一行,matcher嘗試匹配pattern的行,鑒別是一個單行的注釋或者多行的注釋在一行中出現。假如一行匹配pattern,ExtCmnt 將此行寫入另外一個文本文件中。例如,java ExtCmnt ExtCmnt.java out 讀取ExtCmnt.java 文件的每一行,根據pattern來嘗試著一行,將匹配的行輸出到名叫out的文件。 (不要擔心理解文件的讀寫邏輯。我將在將來的文章中explore這些代碼。) 在ExtCmnt執行完成,out 文件包含了以下行:
// ExtCmnt.java
// The following pattern lets this extract multiline comments that
// appear on a single line (e.g., /* same line */) and single-line
// comments (e.g., // some line). Furthermore, the comment may
// appear anywhere on the line.
p = Pattern.compile (".*/\\*.*\\*/|.*//.*$");
if (m.matches ()) /* entire line must match */
finally // Close file.
這個輸出顯示ExtCmnt 并不完美:p = Pattern.compile (".*/\\*.*\\*/|.*//.*$"); 沒有描繪一個注釋。出現在out中的行因為ExtCmnt的matcher匹配了//字符。
關于pattern ".*/\\*.*\\*/|.*//.*$"由一些有趣的事,豎線元字符metacharacter (|)。依照SDK documentation,圓括號元字符在capturing group和 豎線元字符是邏輯操作符號。vertical bar 描述了一個matcher,它使用操作符左側的正則表達式結構來在matcher的文本中定為一個match。假如沒有match存在,matcher使用操作符號右側的正則表達式進行再次的匹配嘗試。
溫習
盡管正則表達式簡化了在text處理程序中pattern匹配的代碼,除非你理解它們,否則你不能有效的在你的程序中使用正則表達式。這篇文章通過介紹給你regex terminology,the java.util.regex 包和示范regex constructs的程序來讓你對正則表達式有一個基本的理解。既然你對regexes有了一個基本的理解,建立在通過閱讀additional articles (see Resources)和學習java.util.regex''s SDK 文檔,那里你可以學習更多的regex constructs ,例如POSIX (Portable Operating System Interface for Unix) 字符類。
我鼓勵你用這篇文章中或者其它以前文章中資料中問題email me。(請保持問題和這個欄目討論的文章相關性。)你的問題和我的回答將出現在相關的學習guides中。)
After writing Java 101 articles for 28 consecutive months, I''m taking a two-month break. I''ll return in May and introduce a series on data structures and algorithms.
About the author
Jeff Friesen has been involved with computers for the past 23 years. He holds a degree in computer science and has worked with many computer languages. Jeff has also taught introductory Java programming at the college level. In addition to writing for JavaWorld, he has written his own Java book for beginners—Java 2 by Example, Second Edition (Que Publishing, 2001; ISBN: 0789725932)—and helped write Using Java 2 Platform, Special Edition (Que Publishing, 2001; ISBN: 0789724685). Jeff goes by the nickname Java Jeff (or JavaJeff). To see what he''s working on, check out his Website at http://www.javajeff.com.
Resources
Download this article''s source code and resource files:
http://www.javaworld.com/javaworld/jw-02-2003/java101/jw-0207-java101.zip
For a glossary specific to this article, homework, and more, see the Java 101 study guide that accompanies this article:
http://www.javaworld.com/javaworld/jw-02-2003/jw-0207-java101guide.html
"Magic with Merlin: Parse Sequences of Characters with the New regex Library," John Zukowski (IBM developerWorks, August 2002) explores java.util.regex''s support for pattern matching and presents a complete example that finds the longest word in a text file:
http://www-106.ibm.com/developerworks/java/library/j-mer0827/
"Matchmaking with Regular Expressions," Benedict Chng (JavaWorld, July 2001) explores regexes in the context of Apache''s Jakarta ORO library:
http://www.javaworld.com/javaworld/jw-07-2001/jw-0713-regex.html
"Regular Expressions and the Java Programming Language," Dana Nourie and Mike McCloskey (Sun Microsystems, August 2002) presents a brief overview of java.util.regex, including five illustrative regex-based applications:
http://developer.java.sun.com/developer/technicalArticles/releases/1.4regex/
In "The Java Platform" (onJava.com), an excerpt from Chapter 4 of O''Reilly''s Java in a Nutshell, 4th Edition, David Flanagan presents short examples of CharSequence and java.util.regex methods:
http://www.onjava.com/pub/a/onjava/excerpt/javanut4_ch04
The Java Tutorial''s "Regular Expressions" lesson teaches the basics of Sun''s java.util.regex package:
http://java.sun.com/docs/books/tutorial/extra/regex/index.html
Wikipedia defines some regex terminology, presents a brief history of regexes, and explores various regex syntaxes:
http://www.wikipedia.org/wiki/Regular_expression
Read Jeff''s previous Java 101 column: "Tools of the Trade, Part 3" (JavaWorld, January 2003):
http://www.javaworld.com/javaworld/jw-01-2003/jw-0103-java101.html?
Check out past Java 101 articles:
http://www.javaworld.com/javaworld/topicalindex/jw-ti-java101.html
Browse the Core Java section of JavaWorld''s Topical Index:
http://www.javaworld.com/channel_content/jw-core-index.shtml
Need some Java help? Visit our Java Beginner discussion:
http://forums.devworld.com/webx?50@@.ee6b804
Java experts answer your toughest Java questions in JavaWorld''s Java Q&A column:
http://www.javaworld.com/javaworld/javaqa/javaqa-index.html
For Tips ''N Tricks, see:
http://www.javaworld.com/javaworld/javatips/jw-javatips.index.html
Sign up for JavaWorld''s free weekly Core Java email newsletter:
http://www.javaworld.com/subscribe
You''ll find a wealth of IT-related articles from our sister publications at IDG.net