java正則表達式語法詳解及其使用代碼實例

原文地址html

 

譯者序(下載代碼)  正則表達式善於處理文本,對匹配、搜索和替換等操做都有意想不到的做用。正因如此,正則表達式如今是做爲程序員七種基本技能之一*,所以學習和使用它在工做中都能達到很高的效率。
  正則表達式應用於程序設計語言中,首次是出如今 Perl 語言,這也讓 Perl 奠基了正則表達式旗手的地位。如今,它已經深刻到了全部的程序設計語言中,在程序設計語言中,正則表達式能夠說是標準配置了。
  Java 中從 JDK 1.4 開始增長了對正則表達式的支持,至此正則表達式成爲了 Java 中的基本類庫,使用時不須要再導入第三方的類庫了。Java 正則表達式的語法來源於象徵着正則表達式標準的 Perl 語言,但也不是徹底相同的,具體的能夠參看 Pattern 類的 API 文檔說明。
  我在一次偶然中發現了位於 java.sun.com 站點上的 Java Tutorial,也在那裏看到了關於 Java 的正則表達式教程,感受它不一樣於其餘的正則表達式教程,文中以大量的匹配實例來進行說明。爲了能讓 Java 學習者能更好地使用正則表達式,就將其完整地譯出了。該教程中所介紹的正則表達式應用僅僅是最爲簡單的(並無徹底地涉及到 Pattern 類支持的全部正則表達式語法,也沒有涉及到高級的應用),適合於從未接觸過或者是還沒有徹底明白正則表達式基礎的學習者。在學習完該教程後,應該對正則表達式有了初步的瞭解,並能熟練地運用 java.util.regex 包中的關於正則表達式的類庫,爲從此學習更高級的正則表達式技術奠基良好的基礎。
  教程中全部的源代碼都在 src 目錄下,能夠直接編譯運行。因爲當前版本的 Java Tutorial 是基於 JDK 6.0 的,所以其中的示例程序也用到了 JDK 6.0 中的新增類庫,但正則表達式在 JDK 1.4 就已經存在了,爲了方便你們使用,改寫了部分的源代碼,源代碼類名中後綴爲「V4」的表示用於 JDK 1.4 或以上版本,「V5」的表示用於 JDK 5.0 或以上版本,沒有這些後綴的類在各個版本中都可以正常使用。
  因爲譯者的水平和技術能力有限,譯稿雖經屢次校對,不免有疏漏之處,敬請你們批評和指正。如有發現不妥之處,請發送郵件至FrankieGao123@gmail.com,我會在 blog 中進行勘誤,謝謝!java

    火龍果頓首!程序員

 

2008 年 2 月 27 日

 

 


 

   *  這是由《程序員》雜誌社評出的,刊登在《程序員》2007 年 3 月刊上。這七種基本技能是:數組,字符串與哈希表、正則表達式、調試、兩門語言、一個開發環境、SQL 語言和編寫軟件的思想。web

目錄
 
 
本文介紹如何使用 java.util.regex API 做爲正則表達式模式匹配。雖說這個包中可被接受的語法參數與 Perl 是類似的,但咱們並不須要掌握 Perl 的語法知識。本教程將從基礎開始,逐層深刻到更多的高級技巧。下面是各章節的主要內容:
0 引言
  粗略地看一下正則表達式,同時也介紹組成 API 的核心類。
1 測試用具
  編寫了一個簡單的應用程序,用於測試正則表達式的模式匹配。
2 字符串
  介紹基本的模式匹配、元字符和引用。
3 字符類
  描述簡單字符類、否認、範圍、並集、交集和差集。
4 預約義字符類
  描述空白字符、字母和數字字符等基本的預約義字符。
5 量詞
  使用貪婪(greedy)、勉強(reluctant)和侵佔(possessive)量詞,來匹配指定表達式 X 的次數。
6 捕獲組
  解釋如何把多個字符做爲一個單獨的單元進行處理。
7 邊界匹配器
  描述行、單詞和輸入的邊界。
8 Pattern 類的方法
  測試了 Pattern 中一些有用的方法,以及探究一些高級的特性,諸如:帶標記的編譯和使用內嵌標記表達式。
9 Matcher 類的方法
  描述了 Matcher 類中一般使用的方法。
10 PatternSyntaxException 類的方法
  描述瞭如何檢查一個 PatternSyntaxException 異常。
11 更多的資源
  要了解更多正則表達式,能夠參考這一節。
12 問題和練習
  鞏固一下本教程所介紹的正則表達式的基本知識,並附有答案。

  爲了區分文檔中的正則表達式和普通字符串,均以\d[abc]{2}的形式表示正則表達式的模式。
 
 
0 引言
 
0.1 什麼是正則表達式?返回目錄  
  正則表達式(regular expressions)是一種描述字符串集的方法,它是以字符串集中各字符串的共有特徵爲依據的。正則表達式能夠用於搜索、編輯或者是操做文本和數據。它超出了 Java 程序設計語言的標準語法,所以有必要去學習特定的語法來構建正則表達式。正則表達式的變化是複雜的,一旦你理解了它們是如何被構造的話,你就能解析或者構建任意的正則表達式了。
  本教程講授 java.util.regex API 所支持的正則表達式語法,以及介紹幾個可運行的例子來講明不一樣的對象間是如何交互的。在正則表達式的世界中,有不一樣風格的選擇,好比:grep[2]、Perl、Tcl、Python、PHP 和 awk。java.util.regex API 中的正則表達式語法與 Perl 中的最爲類似。
0.2 java.util.regex 包是如何描述正則表達式的?
java.util.regex 包主要由三個類所組成:Pattern、Matcher 和 PatternSyntaxException。
  • Pattern 對象表示一個已編譯的正則表達式。Pattern 類沒有提供公共的構造方法。要構建一個模式,首先必須調用公共的靜態 compile 方法,它將返回一個 Pattern 對象。這個方法接受正則表達式做爲第一個參數。本教程的開始部分將教你必需的語法。
  • Matcher 是一個靠着輸入的字符串來解析這個模式和完成匹配操做的對象。與 Pattern 類似,Matcher 也沒有定義公共的構造方法,須要經過調用 Pattern 對象的 matcher 方法來得到一個 Matcher 對象。
  • PatternSyntaxException 對象是一個未檢查異常,指示了正則表達式中的一個語法錯誤。
  本教程的最後幾節課程會詳細地說明各個類。首當其衝的問題是:必須理解正則表達式是如何被構建的,所以下一節引入了一個簡單的測試用具,重複地用於探究它們的語法。
 
1 測試用具
這節給出了一個可重用的測試用具 RegexTestHarness.java,用於探究構建 API 所支持的正則表達式。使用
java RegexTestHarness
這個命令來運行,沒有被接受的命令行參數。這個應用會不停地循環執行下去[3],提示用戶輸入正則表達式和字符串。雖說使用這個測試用具是可選的,但你會發現它用於探究下文所討論的測試用例將更爲方便。
 1 import java.io.Console; 
 2 import java.util.regex.Pattern; 
 3 import java.util.regex.Matcher; 
 4  
 5 public class RegexTestHarness { 
 6  
 7     public static void main(String[] args) { 
 8         Console console = System.console(); 
 9         if (console == null) { 
10             System.err.println("No console."); 
11             System.exit(1); 
12         } 
13        
14         while (true) { 
15             Pattern pattern = Pattern.compile(console.readLine("%nEnter your regex: ")); 
16             Matcher matcher = pattern.matcher(console.readLine("Enter input string to search: ")); 
17             boolean found = false; 
18             while (matcher.find()) { 
19                 console.format("I found the text \"%s\" starting at index %d " + 
20                         "and ending at index %d.%n", 
21                         matcher.group(), matcher.start(), matcher.end()); 
22                 found = true; 
23             } 
24             if (!found) { 
25                 console.format("No match found.%n"); 
26             } 
27         } 
28     } 
29 }

在繼續下一節以前,確認開發環境支持必需的包,並保存和編譯這段代碼。正則表達式

【譯者注】

  因爲當前版本的 Java Tutorial 是基於 JDK 6.0 編寫的,上述的測試用具因爲使用到 JDK 6.0 中新增的類庫(java.io.Console),因此該用具只能在 JDK 6.0 的環境中編譯運行,因爲 Console 訪問操做系統平臺上的控制檯,所以這個測試用具只能在操做系統的字符控制檯中運行,不能運行在 IDE 的控制檯中。
  正則表達式是 JDK 1.4 所增長的類庫,爲了兼容 JDK 1.4 和 JDK 5.0 的版本,從新改寫了這個測試用具,讓其能適用於不一樣的版本。
  JDK 5.0 適用的測試用具(RegexTestHarnessV5.java,該用具能夠在 IDE 中執行),建議 JDK 6.0 環境也採用該用具。express

 1 import java.util.Scanner; 
 2 import java.util.regex.Matcher; 
 3 import java.util.regex.Pattern; 
 4  
 5 public class RegexTestHarnessV5 { 
 6  
 7     public static void main(String[] args) { 
 8         Scanner scanner = new Scanner(System.in); 
 9         while (true) { 
10             System.out.printf("%nEnter your regex: "); 
11             Pattern pattern = Pattern.compile(scanner.nextLine()); 
12             System.out.printf("Enter input string to search: "); 
13             Matcher matcher = pattern.matcher(scanner.nextLine()); 
14             boolean found = false; 
15             while (matcher.find()) { 
16                 System.out.printf( 
17                         "I found the text \"%s\" starting at index %d and ending at index %d.%n", 
18                         matcher.group(), matcher.start(), matcher.end() 
19                     ); 
20                 found = true; 
21             } 
22             if (!found) { 
23                 System.out.printf("No match found.%n"); 
24             } 
25         } 
26     } 
27 }

JDK 1.4 適用的測試用具(RegexTestHarnessV4.java):api

 1 import java.io.BufferedInputStream; 
 2 import java.io.BufferedReader; 
 3 import java.io.IOException; 
 4 import java.io.InputStreamReader; 
 5 import java.util.regex.Matcher; 
 6 import java.util.regex.Pattern; 
 7  
 8 public class RegexTestHarnessV4 { 
 9  
10     public static void main(String[] args) throws IOException { 
11         BufferedReader br = new BufferedReader( 
12                 new InputStreamReader(new BufferedInputStream(System.in)) 
13             ); 
14         while (true) { 
15             System.out.print("\nEnter your regex: "); 
16             Pattern pattern = Pattern.compile(br.readLine()); 
17             System.out.print("Enter input string to search: "); 
18             Matcher matcher = pattern.matcher(br.readLine()); 
19             boolean found = false; 
20             while (matcher.find()) { 
21                 System.out.println("I found the text \"" + matcher.group() + 
22                         "\" starting at index " + matcher.start() + 
23                         " and ending at index " + matcher.end() + 
24                         "."); 
25                 found = true; 
26             } 
27             if (!found) { 
28                 System.out.println("No match found."); 
29             } 
30         } 
31     } 
32 }

2 字符串數組

在大多數的狀況下,API所支持模式匹配的基本形式是匹配字符串,若是正則表達式是foo,輸入的字符串也是 foo,這個匹配將會是成功的,由於這兩個字符串是相同的。試着用測試用具來測試一下:瀏覽器

1 Enter your regex: foo
2 Enter input string to search: foo
3 I found the text "foo" starting at index 0 and ending at index 3.

 

  結果確實是成功的。注意當輸入的字符串是 3 個字符長度的時候,開始的索引是 0,結束的索引是 3。這個是約定俗成的,範圍包括開始的索引,不包括結束的索引,以下圖所示:app


圖 1 字符串「foo」的單元格編號和索引值 [4]

  字符串中的每個字符位於其自身的單元格(cell)中,在每一個單元格之間有索引指示位。字符串「foo」始於索引 0 處,止於索引 3 處,即便是這些字符它們本身僅佔據了 0、1 和 2 號單元格。
  就子序列匹配而言,你會注意到一些重疊,下一次匹配開始索引與前一次匹配的結束索引是相同的:

Enter your regex: foo
Enter input string to search: foofoofoo
I found the text "foo" starting at index 0 and ending at index 3.
I found the text "foo" starting at index 3 and ending at index 6.
I found the text "foo" starting at index 6 and ending at index 9.

 

2.1 元字符 返回目錄

  API 也支持許多能夠影響模式匹配的特殊字符。把正則表達式改成cat.並輸入字符串「cats」,輸出以下所示:

1 Enter your regex: cat.
2 Enter input string to search: cats
3 I found the text "cats" starting at index 0 and ending at index 4.

雖然在輸入的字符串中沒有點(.),但這個匹配仍然是成功的。這是因爲點(.)是一個元字符(metacharacters)(被這個匹配翻譯成了具備特殊意義的字符了)。這個例子爲何能匹配成功的緣由在於,元字符.指的是「任意字符」。
  API 所支持的元字符有:([{\^-$|}])?*+.

注意:在學習過更多的如何構建正則表達式後,你會碰到這些狀況:上面的這些特殊字符不該該被處理爲元字符。然而也可以使用這個清單來檢查一個特殊的字符是否會被認爲是元字符。例如,字符 !、@ 和 # 決不會有特殊的意義。

  有兩種方法能夠強制將元字符處理成爲普通字符:
  1. 在元字符前加上反斜線(\);
  2. 把它放在\Q(引用開始)和\E(引用結束)之間[5]。在使用這種技術時,\Q\E能被放於表達式中的任何位置(假設先出現\Q[6]

3 字符類 返回目錄

  若是你曾看過 Pattern 類的說明,會看到一些構建正則表達式的概述。在這一節中你會發現下面的一些表達式:

字符類
[abc] a, b 或 c(簡單類)
[^abc] 除 a, b 或 c 以外的任意字符(取反)
[a-zA-Z] a 到 z,或 A 到 Z,包括(範圍)
[a-d[m-p]] a 到 d,或 m 到 p:[a-dm-p](並集)
[a-z&&[def]] d,e 或 f(交集)
[a-z&&[^bc]] 除 b 和 c 以外的 a 到 z 字符:[ad-z](差集)
[a-z&&[^m-p]] a 到 z,而且不包括 m 到 p:[a-lq-z](差集)

  左邊列指定正則表達式構造,右邊列描述每一個構造的匹配的條件。

注意:「字符類(character class)」這個詞中的「類(class)」指的並非一個 .class 文件。在正則表達式的語義中,字符類是放在方括號裏的字符集,指定了一些字符中的一個能被給定的字符串所匹配。

3.1 簡單類(Simple Classes) 返回目錄

  字符類最基本的格式是把一些字符放在一對方括號內。例如:正則表達式[bcr]at會匹配「bat」、「cat」或者「rat」,這是因爲其定義了一個字符類(接受「b」、「c」或「r」中的一個字符)做爲它的首字符。

 1 Enter your regex: [bcr]at
 2 Enter input string to search: bat
 3 I found the text "bat" starting at index 0 and ending at index 3.
 4 
 5 Enter your regex: [bcr]at
 6 Enter input string to search: cat
 7 I found the text "cat" starting at index 0 and ending at index 3.
 8 
 9 Enter your regex: [bcr]at
10 Enter input string to search: rat
11 I found the text "rat" starting at index 0 and ending at index 3.
12 
13 Enter your regex: [bcr]at
14 Enter input string to search: hat
15 No match found.

在上面的例子中,在第一個字符匹配字符類中所定義字符中的一個時,整個匹配就是成功的。

3.1.1 否認 返回目錄

  要匹配除那些列表以外全部的字符時,能夠在字符類的開始處加上^元字符,這種就被稱爲否認(negation)。

 1 Enter your regex: [^bcr]at
 2 Enter input string to search: bat
 3 No match found.
 4 
 5 Enter your regex: [^bcr]at
 6 Enter input string to search: cat
 7 No match found.
 8 
 9 Enter your regex: [^bcr]at
10 Enter input string to search: rat
11 No match found.
12 
13 Enter your regex: [^bcr]at
14 Enter input string to search: hat
15 I found the text "hat" starting at index 0 and ending at index 3.

在輸入的字符串中的第一個字符不包含在字符類中所定義字符中的一個時,匹配是成功的。

3.1.2 範圍 返回目錄

  有時會想要定義一個包含值範圍的字符類,諸如,「a 到 h」的字母或者是「1 到 5」的數字。指定一個範圍,只要在被匹配的首字符和末字符間插入-元字符,好比:[1-5]或者是[a-h]。也能夠在類裏每一個的邊上放置不一樣的範圍來提升匹配的可能性,例如:[a-zA-Z]將會匹配 a 到 z(小寫字母)或者 A 到 Z(大寫字母)中的任何一個字符。
  下面是一些範圍和否認的例子:

 1 Enter your regex: [a-c]
 2 Enter input string to search: a
 3 I found the text "a" starting at index 0 and ending at index 1.
 4 
 5 Enter your regex: [a-c]
 6 Enter input string to search: b
 7 I found the text "b" starting at index 0 and ending at index 1.
 8 
 9 Enter your regex: [a-c]
10 Enter input string to search: c
11 I found the text "c" starting at index 0 and ending at index 1.
12 
13 Enter your regex: [a-c]
14 Enter input string to search: d
15 No match found.
16 
17 Enter your regex: foo[1-5]
18 Enter input string to search: foo1
19 I found the text "foo1" starting at index 0 and ending at index 4.
20 
21 Enter your regex: foo[1-5]
22 Enter input string to search: foo5
23 I found the text "foo5" starting at index 0 and ending at index 4.
24 
25 Enter your regex: foo[1-5]
26 Enter input string to search: foo6
27 No match found.
28 
29 Enter your regex: foo[^1-5]
30 Enter input string to search: foo1
31 No match found.
32 
33 Enter your regex: foo[^1-5]
34 Enter input string to search: foo6
35 I found the text "foo6" starting at index 0 and ending at index 4.

3.1.3 並集    

  可使用並集(union)來建一個由兩個或兩個以上字符類所組成的單字符類。構建一個並集,只要在一個字符類的邊上嵌套另一個,好比:[0-4[6-8]],這種奇特方式構建的並集字符類,能夠匹配 0,1,2,3,4,6,7,8 這幾個數字。

 1 Enter your regex: [0-4[6-8]]
 2 Enter input string to search: 0
 3 I found the text "0" starting at index 0 and ending at index 1.
 4 
 5 Enter your regex: [0-4[6-8]]
 6 Enter input string to search: 5
 7 No match found.
 8 
 9 Enter your regex: [0-4[6-8]]
10 Enter input string to search: 6
11 I found the text "6" starting at index 0 and ending at index 1.
12 
13 Enter your regex: [0-4[6-8]]
14 Enter input string to search: 8
15 I found the text "8" starting at index 0 and ending at index 1.
16 
17 Enter your regex: [0-4[6-8]]
18 Enter input string to search: 9
19 No match found.

3.1.4 交集

  建一個僅僅匹配自身嵌套類中公共部分字符的字符類時,能夠像[0-9&&[345]]中那樣使用&&。這種方式構建出來的交集(intersection)簡單字符類,僅僅以匹配兩個字符類中的 3,4,5 共有部分。

Enter your regex: [0-9&&[345]]
Enter input string to search: 3
I found the text "3" starting at index 0 and ending at index 1.

Enter your regex: [0-9&&[345]]
Enter input string to search: 4
I found the text "4" starting at index 0 and ending at index 1.

Enter your regex: [0-9&&[345]]
Enter input string to search: 5
I found the text "5" starting at index 0 and ending at index 1.

Enter your regex: [0-9&&[345]]
Enter input string to search: 2
No match found.

Enter your regex: [0-9&&[345]]
Enter input string to search: 6
No match found.

下面演示兩個範圍交集的例子:

 1 Enter your regex: [2-8&&[4-6]]
 2 Enter input string to search: 3
 3 No match found.
 4 
 5 Enter your regex: [2-8&&[4-6]]
 6 Enter input string to search: 4
 7 I found the text "4" starting at index 0 and ending at index 1.
 8 
 9 Enter your regex: [2-8&&[4-6]]
10 Enter input string to search: 5
11 I found the text "5" starting at index 0 and ending at index 1.
12 
13 Enter your regex: [2-8&&[4-6]]
14 Enter input string to search: 6
15 I found the text "6" starting at index 0 and ending at index 1.
16 
17 Enter your regex: [2-8&&[4-6]]
18 Enter input string to search: 7
19 No match found.

3.1.5 差集

  最後,可使用差集(subtraction)來否認一個或多個嵌套的字符類,好比:[0-9&&[^345]],這個是構建一個匹配除 3,4,5 以外全部 0 到 9 間數字的簡單字符類。

 1 Enter your regex: [0-9&&[^345]]
 2 Enter input string to search: 2
 3 I found the text "2" starting at index 0 and ending at index 1.
 4 
 5 Enter your regex: [0-9&&[^345]]
 6 Enter input string to search: 3
 7 No match found.
 8 
 9 Enter your regex: [0-9&&[^345]]
10 Enter input string to search: 4
11 No match found.
12 
13 Enter your regex: [0-9&&[^345]]
14 Enter input string to search: 5
15 No match found.
16 
17 Enter your regex: [0-9&&[^345]]
18 Enter input string to search: 6
19 I found the text "6" starting at index 0 and ending at index 1.
20 
21 Enter your regex: [0-9&&[^345]]
22 Enter input string to search: 9
23 I found the text "9" starting at index 0 and ending at index 1.

到此爲止,已經涵蓋了如何創建字符類的部分。在繼續下一節以前,能夠試着回想一下那張字符類表

4 預約義字符類

Pattern 的 API 包有許多有用的預約義字符類(predefined character classes),提供了經常使用正則表達式的簡寫形式。
預約義字符類
. 任何字符(匹配或者不匹配行結束符)
\d 數字字符:[0-9]
\D 非數字字符:[^0-9]
\s 空白字符:[\t\n\x0B\f\r]
\S 非空白字符:[^\s]
\w 單詞字符:[a-zA-Z_0-9]
\W 非單詞字符:[^\w]
  上表中,左列是構造右列字符類的簡寫形式。例如: \d指的是數字範圍(0~9), \w指的是單詞字符(任何大小寫字母、下劃線或者是數字)。不管什麼時候都有可能使用預約義字符類,它可使代碼更易閱讀,更易從難看的字符類中排除錯誤。
  以反斜線( \)開始的構造稱爲轉義構造(escaped constructs)。回顧一下在  字符串 一節中的轉義構造,在那裏咱們說起了使用反斜線,以及用於引用的 \Q\E。在字符串中使用轉義構造,必須在一個反斜線前再增長一個反斜用於字符串的編譯,例如:
001
private final String REGEX = "\\d";        // 單個數字
  這個例子中 \d是正則表達式,另外的那個反斜線是用於代碼編譯所必需的。可是測試用具讀取的表達式,是直接從控制檯中輸入的,所以不須要那個多出來的反斜線。
  下面的例子說明了預字義字符類的用法:
 1 Enter your regex: .
 2 Enter input string to search: @
 3 I found the text "@" starting at index 0 and ending at index 1.
 4 
 5 Enter your regex: .
 6 Enter input string to search: 1
 7 I found the text "1" starting at index 0 and ending at index 1.
 8 
 9 Enter your regex: .
10 Enter input string to search: a
11 I found the text "a" starting at index 0 and ending at index 1.
12 
13 Enter your regex: \d
14 Enter input string to search: 1
15 I found the text "1" starting at index 0 and ending at index 1.
16 
17 Enter your regex: \d
18 Enter input string to search: a
19 No match found.
20 
21 Enter your regex: \D
22 Enter input string to search: 1
23 No match found.
24 
25 Enter your regex: \D
26 Enter input string to search: a
27 I found the text "a" starting at index 0 and ending at index 1.
28 
29 Enter your regex: \s
30 Enter input string to search:  
31 I found the text " " starting at index 0 and ending at index 1.
32 
33 Enter your regex: \s
34 Enter input string to search: a
35 No match found.
36 
37 Enter your regex: \S
38 Enter input string to search:  
39 No match found.
40 
41 Enter your regex: \S
42 Enter input string to search: a
43 I found the text "a" starting at index 0 and ending at index 1.
44 
45 Enter your regex: \w
46 Enter input string to search: a
47 I found the text "a" starting at index 0 and ending at index 1.
48 
49 Enter your regex: \w
50 Enter input string to search: !
51 No match found.
52 
53 Enter your regex: \W
54 Enter input string to search: a
55 No match found.
56 
57 Enter your regex: \W
58 Enter input string to search: !
59 I found the text "!" starting at index 0 and ending at index 1.

在開始的三個例子中,正則表達式是簡單的,.(「點」元字符)表示「任意字符」,所以,在全部的三個例子(隨意地選取了「@」字符,數字和字母)中都是匹配成功的。在接下來的例子中,都使用了預約義字符類表格中的單個正則表達式構造。你應該能夠根據這張表指出前面每一個匹配的邏輯:
  \d 匹配數字字符
  \s 匹配空白字符
  \w 匹配單詞字符
  也可使用意思正好相反的大寫字母:
  \D 匹配非數字字符
  \S 匹配非空白字符
  \W 匹配非單詞字符

5 量詞

這一節咱們來看一下貪婪(greedy)、勉強(reluctant)和侵佔(possessive)量詞,來匹配指定表達式X的次數。
  量詞(quantifiers)容許指定匹配出現的次數,方便起見,當前 Pattern API 規範下,描述了貪婪、勉強和侵佔三種量詞。首先粗略地看一下,量詞X?X??X?+都容許匹配 X 零次或一次,精確地作一樣的事情,但它們之間有着細微的不一樣之處,在這節結束前會進行說明。
量 詞 種 類 意  義
貪婪 勉強 侵佔
X? X?? X?+ 匹配 X 零次或一次
X* X*? X*+ 匹配 X 零次或屢次
X+ X+? X++ 匹配 X 一次或屢次
X{n} X{n}? X{n}+ 匹配 X n 次
X{n,} X{n,}? X{n,}+ 匹配 X 至少 n 次
X{n,m} X{n,m}? X{n,m}+ 匹配 X 至少 n 次,但很少於 m 次
  那咱們如今就從貪婪量詞開始,構建三個不一樣的正則表達式:字母a後面跟着?*+。接下來看一下,用這些表達式來測試輸入的字符串是空字符串時會發生些什麼:
 1 Enter your regex: a?
 2 Enter input string to search: 
 3 I found the text "" starting at index 0 and ending at index 0.
 4 
 5 Enter your regex: a*
 6 Enter input string to search: 
 7 I found the text "" starting at index 0 and ending at index 0.
 8 
 9 Enter your regex: a+
10 Enter input string to search: 
11 No match found.

5.1 零長度匹配

  在上面的例子中,開始的兩個匹配是成功的,這是由於表達式a?a*都容許字符出現零次。就目前而言,這個例子不像其餘的,也許你注意到了開始和結束的索引都是 0。輸入的空字符串沒有長度,所以該測試簡單地在索引 0 上匹配什麼都沒有,諸如此類的匹配稱之爲零長度匹配(zero-length matches)。零長度匹配會出如今如下幾種狀況:輸入空的字符串、在輸入字符串的開始處、在輸入字符串最後字符的後面,或者是輸入字符串中任意兩個字符之間。因爲它們開始和結束的位置有着相同的索引,所以零長度匹配是容易被發現的。
  咱們來看一下關於零長度匹配更多的例子。把輸入的字符串改成單個字符「a」,你會注意到一些有意思的事情:

 1 Enter your regex: a?
 2 Enter input string to search: a
 3 I found the text "a" starting at index 0 and ending at index 1.
 4 I found the text "" starting at index 1 and ending at index 1.
 5 
 6 Enter your regex: a*
 7 Enter input string to search: a
 8 I found the text "a" starting at index 0 and ending at index 1.
 9 I found the text "" starting at index 1 and ending at index 1.
10 
11 Enter your regex: a+
12 Enter input string to search: a
13 I found the text "a" starting at index 0 and ending at index 1.

全部的三個量詞都是用來尋找字母「a」的,可是前面兩個在索引 1 處找到了零長度匹配,也就是說,在輸入字符串最後一個字符的後面。回想一下,匹配把字符「a」看做是位於索引 0 和索引 1 之間的單元格中,而且測試用具一直循環下去直到再也不有匹配爲止。依賴於所使用的量詞不一樣,最後字符後面的索引「什麼也沒有」的存在能夠或者不能夠觸發一個匹配。
  如今把輸入的字符串改成一行 5 個「a」時,會獲得下面的結果:

 1 Enter your regex: a?
 2 Enter input string to search: aaaaa
 3 I found the text "a" starting at index 0 and ending at index 1.
 4 I found the text "a" starting at index 1 and ending at index 2.
 5 I found the text "a" starting at index 2 and ending at index 3.
 6 I found the text "a" starting at index 3 and ending at index 4.
 7 I found the text "a" starting at index 4 and ending at index 5.
 8 I found the text "" starting at index 5 and ending at index 5.
 9 
10 Enter your regex: a*
11 Enter input string to search: aaaaa
12 I found the text "aaaaa" starting at index 0 and ending at index 5.
13 I found the text "" starting at index 5 and ending at index 5.
14 
15 Enter your regex: a+
16 Enter input string to search: aaaaa
17 I found the text "aaaaa" starting at index 0 and ending at index 5.

  

 1 Enter your regex: a?
 2 Enter input string to search: ababaaaab
 3 I found the text "a" starting at index 0 and ending at index 1.
 4 I found the text "" starting at index 1 and ending at index 1.
 5 I found the text "a" starting at index 2 and ending at index 3.
 6 I found the text "" starting at index 3 and ending at index 3.
 7 I found the text "a" starting at index 4 and ending at index 5.
 8 I found the text "a" starting at index 5 and ending at index 6.
 9 I found the text "a" starting at index 6 and ending at index 7.
10 I found the text "a" starting at index 7 and ending at index 8.
11 I found the text "" starting at index 8 and ending at index 8.
12 I found the text "" starting at index 9 and ending at index 9.
13 
14 Enter your regex: a*
15 Enter input string to search: ababaaaab
16 I found the text "a" starting at index 0 and ending at index 1.
17 I found the text "" starting at index 1 and ending at index 1.
18 I found the text "a" starting at index 2 and ending at index 3.
19 I found the text "" starting at index 3 and ending at index 3.
20 I found the text "aaaa" starting at index 4 and ending at index 8.
21 I found the text "" starting at index 8 and ending at index 8.
22 I found the text "" starting at index 9 and ending at index 9.
23 
24 Enter your regex: a+
25 Enter input string to search: ababaaaab
26 I found the text "a" starting at index 0 and ending at index 1.
27 I found the text "a" starting at index 2 and ending at index 3.
28 I found the text "aaaa" starting at index 4 and ending at index 8.

  即便字母「b」在單元格 一、三、8 中出現,但在這些位置上的輸出報告了零長度匹配。正則表達式a?不是特地地去尋找字母「b」,它僅僅是去找字母「a」存在或者其中缺乏的。若是量詞容許匹配「a」零次,任何輸入的字符不是「a」時將會做爲零長度匹配。在前面的例子中,根據討論的規則保證了 a 被匹配。
  對於要精確地匹配一個模式 n 次時,能夠簡單地在一對花括號內指定一個數值:

在「a」出現零次或一次時,表達式a?尋找到所匹配的每個字符。表達式a*找到了兩個單獨的匹配:第一次匹配到全部的字母「a」,而後是匹配到最後一個字符後面的索引 5。最後,a+匹配了全部出現的字母「a」,忽略了在最後索引處「什麼都沒有」的存在。
  在這裏,你也許會感到疑惑,開始的兩個量詞在遇到除了「a」的字母時會有什麼結果。例如,在「ababaaaab」中遇到了字母「b」會發生什麼呢?
  下面咱們來看一下

 1 Enter your regex: a{3}
 2 Enter input string to search: aa
 3 No match found.
 4 
 5 Enter your regex: a{3}
 6 Enter input string to search: aaa
 7 I found the text "aaa" starting at index 0 and ending at index 3.
 8 
 9 Enter your regex: a{3}
10 Enter input string to search: aaaa
11 I found the text "aaa" starting at index 0 and ending at index 3.

  這裏,正則表肯定式a{3}在一行中尋找連續出現三次的字母「a」。第一次測試失敗的起因在於,輸入的字符串沒有足夠的 a 用來匹配;第二次測試輸出的字符串正好包括了三個「a」,觸發了一次匹配;第三次測試也觸發了一次匹配,這是因爲在輸出的字符串的開始部分正好有三個「a」。接下來的事情與第一次的匹配是不相關的,若是這個模式將在這一點後繼續出現,那它將會觸發接下來的匹配:

1 Enter your regex: a{3}
2 Enter input string to search: aaaaaaaaa
3 I found the text "aaa" starting at index 0 and ending at index 3.
4 I found the text "aaa" starting at index 3 and ending at index 6.
5 I found the text "aaa" starting at index 6 and ending at index 9.

對於須要一個模式出現至少 n 次時,能夠在這個數字後面加上一個逗號(,):

Enter your regex: a{3,}
Enter input string to search: aaaaaaaaa
I found the text "aaaaaaaaa" starting at index 0 and ending at index 9.

  輸入同樣的字符串,此次測試僅僅找到了一個匹配,這是因爲一箇中有九個「a」知足了「至少」三個「a」的要求。
  最後,對於指定出現次數的上限,能夠在花括號添加第二個數字。

1 Enter your regex: a{3,6} // 尋找一行中至少連續出現 3 個(但很少於 6 個)「a」
2 Enter input string to search: aaaaaaaaa
3 I found the text "aaaaaa" starting at index 0 and ending at index 6.
4 I found the text "aaa" starting at index 6 and ending at index 9.

這裏,第一次匹配在 6 個字符的上限時被迫終止了。第二個匹配包含了剩餘的三個 a(這是匹配所容許最小的字符個數)。若是輸入的字符串再少掉一個字母,這時將不會有第二個匹配,以後僅剩餘兩個 a。

5.2 捕獲組和字符類中的量詞

  到目前爲止,僅僅測試了輸入的字符串包括一個字符的量詞。實際上,量詞僅僅可能附在一個字符後面一次,所以正則表達式abc+的意思就是「a 後面接着 b,再接着一次或者屢次的 c」,它的意思並非指abc一次或者屢次。然而,量詞也可能附在字符類和捕獲組的後面,好比,[abc]+表示一次或者屢次的 a 或 b 或 c,(abc)+表示一次或者屢次的「abc」組。
  咱們來指定(dog)組在一行中三次進行說明。

Enter your regex: (dog){3}
Enter input string to search: dogdogdogdogdogdog
I found the text "dogdogdog" starting at index 0 and ending at index 9.
I found the text "dogdogdog" starting at index 9 and ending at index 18.

Enter your regex: dog{3}
Enter input string to search: dogdogdogdogdogdog
No match found.

上面的第一個例子找到了三個匹配,這是因爲量詞用在了整個捕獲組上。然而,把圓括號去掉,這時的量詞{3}如今僅用在了字母「g」上,從而致使這個匹配失敗。
  相似地,也能把量詞應用於整個字符類:

 1 Enter your regex: [abc]{3}
 2 Enter input string to search: abccabaaaccbbbc
 3 I found the text "abc" starting at index 0 and ending at index 3.
 4 I found the text "cab" starting at index 3 and ending at index 6.
 5 I found the text "aaa" starting at index 6 and ending at index 9.
 6 I found the text "ccb" starting at index 9 and ending at index 12.
 7 I found the text "bbc" starting at index 12 and ending at index 15.
 8 
 9 Enter your regex: abc{3}
10 Enter input string to search: abccabaaaccbbbc
11 No match found.

的第一個例子中,量詞{3}應用在了整個字符類上,可是第二個例子這個量詞僅用在字母「c」上。

5.3 貪婪、勉強和侵佔量詞間的不一樣

  在貪婪、勉強和侵佔三個量詞間有着細微的不一樣。
  貪婪量詞之因此稱之爲「貪婪的」,這是因爲它們強迫匹配器讀入(或者稱之爲吃掉)整個輸入的字符串,來優先嚐試第一次匹配,若是第一次嘗試匹配(對於整個輸入的字符串)失敗,匹配器會經過回退整個字符串的一個字符再一次進行嘗試,不斷地進行處理直到找到一個匹配,或者左邊沒有更多的字符來用於回退了。賴於在表達式中使用的量詞,最終它將嘗試地靠着 1 或 0 個字符的匹配。
  可是,勉強量詞采用相反的途徑:從輸入字符串的開始處開始,所以每次勉強地吞噬一個字符來尋找匹配,最終它們會嘗試整個輸入的字符串。
  最後,侵佔量詞始終是吞掉整個輸入的字符串,嘗試着一次(僅有一次)匹配。不像貪婪量詞那樣,侵佔量詞毫不會回退,即便這樣作是容許所有的匹配成功。
  爲了說明一下,看看輸入的字符串是 xfooxxxxxxfoo 時。

Enter your regex: .*foo  // 貪婪量詞
Enter input string to search: xfooxxxxxxfoo
I found the text "xfooxxxxxxfoo" starting at index 0 and ending at index 13.

Enter your regex: .*?foo  // 勉強量詞
Enter input string to search: xfooxxxxxxfoo
I found the text "xfoo" starting at index 0 and ending at index 4.
I found the text "xxxxxxfoo" starting at index 4 and ending at index 13.

Enter your regex: .*+foo // 侵佔量詞
Enter input string to search: xfooxxxxxxfoo
No match found.

第一個例子使用貪婪量詞.*,尋找緊跟着字母「f」「o」「o」的「任何東西」零次或者屢次。因爲量詞是貪婪的,表達式的.*部分第一次「吃掉」整個輸入的字符串。在這一點,所有表達式不能成功地進行匹配,這是因爲最後三個字母(「f」「o」「o」)已經被消耗掉了。那麼匹配器會慢慢地每次回退一個字母,直到返還的「foo」在最右邊出現,這時匹配成功而且搜索終止。
  然而,第二個例子採用勉強量詞,所以經過首次消耗「什麼也沒有」做爲開始。因爲「foo」並無出如今字符串的開始,它被強迫吞掉第一個字母(「x」),在 0 和 4 處觸發了第一個匹配。測試用具會繼續處理,直到輸入的字符串耗盡爲止。在 4 和 13 找到了另一個匹配。
  第三個例子的量詞是侵佔,因此在尋找匹配時失敗了。在這種狀況下,整個輸入的字符串被.*+消耗了,什麼都沒有剩下來知足表達式末尾的「foo」。
  你能夠在想抓取全部的東西,且決不回退的狀況下使用侵佔量詞,在這種匹配不是當即被發現的狀況下,它將會優於等價的貪婪量詞。

6 捕獲組

在上一節中,學習了每次如何把量詞放在一個字符、字符類或者捕獲組中。到目前爲止,尚未詳細地討論過捕獲組的概念。
  捕獲組(capturing group)是將多個字符做爲單獨的單元來對待的一種方式。構建它們能夠經過把字符放在一對圓括號中而成爲一組。例如,正則表達式(dog)建了單個的組,包括字符「d」「o」和「g」。匹配捕獲組輸入的字符串部分將會存放於內存中,稍後經過反向引用再次調用。(在 6.2 節 中將會討論反向引用)

6.1 編號方式返回目錄

  在 Pattern 的 API 描述中,捕獲組經過從左至右計算開始的圓括號進行編號。例如,在表達式((A)(B(C)))中,有下面的四組:
  1. ((A)(B(C)))
  2. (A)
  3. (B(C))
  4. (C)
  要找出當前的表達式中有多少組,經過調用 Matcher 對象的 groupCount 方法。groupCount 方法返回 int 類型值,表示當前 Matcher 模式中捕獲組的數量。例如,groupCount 返回 4 時,表示模式中包含有 4 個捕獲組。
  有一個特別的組——組 0,它表示整個表達式。這個組不包括在 groupCount 的報告範圍內。以(?開始的組是純粹的非捕獲組(non-capturing group),它不捕獲文本,也不做爲組總數而計數。(能夠看 8 Pattern 類的方法 一節中非捕獲組的例子。)
  Matcher 中的一些方法,能夠指定 int 類型的特定組號做爲參數,所以理解組是如何編號的是尤其重要的。
  :返回以前的匹配操做期間,給定組所捕獲的子序列的初始索引。
  :返回以前的匹配操做期間,給定組所捕獲子序列的最後字符索引加 1。
  :返回以前的匹配操做期間,經過給定組而捕獲的輸入子序列。

6.2 反向引用

匹配輸入字符串的捕獲組部分會存放在內存中,經過反向引用(backreferences)稍後再調用。在正則表達式中,反向引用使用反斜線(\)後跟一個表示須要再調用組號的數字來表示。例如,表達式(\d\d)定義了匹配一行中的兩個數字的捕獲組,經過反向引用\1,表達式稍候會被再次調用。
  匹配兩個數字,且後面跟着兩個徹底相同的數字時,就可使用(\d\d)\1做爲正則表達式:

1 Enter your regex: (\d\d)\1
2 Enter input string to search: 1212
3 I found the text "1212" starting at index 0 and ending at index 4.

若是更改最後的兩個數字,這時匹配就會失敗:

1 Enter your regex: (\d\d)\1
2 Enter input string to search: 1234
3 No match found.

對於嵌套的捕獲組而言,反向引用採用徹底相同的方式進行工做,即指定一個反斜線加上須要被再次調用的組號。

7 邊界匹配器

就目前而言,咱們的興趣在於指定輸入字符串中某些位置是否有匹配,尚未考慮到字符串的匹配產生在什麼地方。
  經過指定一些邊界匹配器(boundary matchers)的信息,可使模式匹配更爲精確。好比說你對某個特定的單詞感興趣,而且它只出如今行首或者是行尾時。又或者你想知道匹配發生在單詞邊界(word boundary),或者是上一個匹配的尾部。
  下表中列出了全部的邊界匹配器及其說明。

邊界匹配器
^ 行首
$ 行尾
\b 單詞邊界
\B 非單詞邊界
\A 輸入的開頭
\G 上一個匹配的結尾
\Z 輸入的結尾,僅用於最後的結束符(若是有的話)
\z 輸入的結尾

  接下來的例子中,說明了^$邊界匹配器的用法。注意上表中,^匹配行首,$匹配行尾。

 1 Enter your regex: ^dog$
 2 Enter input string to search: dog
 3 I found the text "dog" starting at index 0 and ending at index 3.
 4 
 5 Enter your regex: ^dog$
 6 Enter input string to search:       dog
 7 No match found.
 8 
 9 Enter your regex: \s*dog$
10 Enter input string to search:             dog
11 I found the text "            dog" starting at index 0 and ending at index 15.
12 
13 Enter your regex: ^dog\w*
14 Enter input string to search: dogblahblah
15 I found the text "dogblahblah" starting at index 0 and ending at index 11.

第一個例子的匹配是成功的,這是由於模式佔據了整個輸入的字符串。第二個例子失敗了,是因爲輸入的字符串在開始部分包含了額外的空格。第三個例子指定的表達式是不限的空格,後跟着在行尾的 dog。第四個例子,須要 dog 放在行首,後面跟的是不限數量的單詞字符。
  對於檢查一個單詞開始和結束的邊界模式(用於長字符串裏子字符串),這時能夠在兩邊使用\b,例如\bdog\b

1 Enter your regex: \bdog\b
2 Enter input string to search: The dog plays in the yard.
3 I found the text "dog" starting at index 4 and ending at index 7.
4 
5 Enter your regex: \bdog\b
6 Enter input string to search: The doggie plays in the yard.
7 No match found.

對於匹配非單詞邊界的表達式,可使用\B來代替:

1 Enter your regex: \bdog\B
2 Enter input string to search: The dog plays in the yard.
3 No match found.
4 
5 Enter your regex: \bdog\B
6 Enter input string to search: The doggie plays in the yard.
7 I found the text "dog" starting at index 4 and ending at index 7.

對於須要匹配僅出如今前一個匹配的結尾,可使用\G

1 Enter your regex: dog
2 Enter input string to search: dog dog
3 I found the text "dog" starting at index 0 and ending at index 3.
4 I found the text "dog" starting at index 4 and ending at index 7.
5 
6 Enter your regex: \Gdog
7 Enter input string to search: dog dog
8 I found the text "dog" starting at index 0 and ending at index 3.

這裏的第二個例子僅找到了一個匹配,這是因爲第二次出現的「dog」不是在前一個匹配結尾的開始。[7]

8 Pattern 類的方法

  到目前爲止,僅使用測試用具來創建最基本的 Pattern 對象。在這一節中,咱們將探討一些諸如使用標誌構建模式、使用內嵌標誌表達式等高級的技術。同時也探討了一些目前尚未討論過的其餘有用的方法。

8.1 使用標誌構建模式

  Pattern 類定義了備用的 compile 方法,用於接受影響模式匹配方式的標誌集。標誌參數是一個位掩碼,能夠是下面公共靜態字段中的任意一個:

Pattern.CANON_EQ  啓用規範等價。在指定此標誌後,當且僅當在其完整的規範分解匹配時,兩個字符被視爲匹配。例如,表達式a\u030A[8]在指定此標誌後,將匹配字符串「\u00E5」(即字符 å)。默認狀況下,匹配不會採用規範等價。指定此標誌可能會對性能會有必定的影響。

Pattern.CASE_INSENSITIVE

  啓用不區分大小寫匹配。默認狀況下,僅匹配 US-ASCII 字符集中的字符。Unicode 感知(Unicode-aware)的不區分大小寫匹配,能夠經過指定 UNICODE_CASE 標誌連同此標誌來啓用。不區分大小寫匹配也能經過內嵌標誌表達式(?i)來啓用。指定此標誌可能會對性能會有必定的影響。

Pattern.COMMENTS

  模式中容許存在空白和註釋。在這種模式下,空白和以#開始的直到行尾的內嵌註釋會被忽略。註釋模式也能經過內嵌標誌表達式(?x)來啓用。

Pattern.DOTALL

  啓用 dotall 模式。在 dotall 模式下,表達式.匹配包括行結束符在內的任意字符。默認狀況下,表達式不會匹配行結束符。dotall 模式也經過內嵌標誌表達式(?x)來啓用。[s 是「單行(single-line)」模式的助記符,與 Perl 中的相同。]

Pattern.LITERAL

  啓用模式的字面分析。指定該標誌後,指定模式的輸入字符串做爲字面上的字符序列來對待。輸入序列中的元字符和轉義字符不具備特殊的意義了。CASE_INSENSITIVE 和 UNICODE_CASE 與此標誌一塊兒使用時,會對匹配產生必定的影響。其餘的標誌就變得多餘了。啓用字面分析沒有內嵌標誌表達式。

Pattern.MULTILINE

  啓用多行(multiline)模式。在多行模式下,表達式^$分別匹配輸入序列行結束符前面和行結束符的前面。默認狀況下,表達式僅匹配整個輸入序列的開始和結尾。多行模式也能經過內嵌標誌表達式(?m)來啓用。

Pattern.UNICODE_CASE

  啓用可摺疊感知 Unicode(Unicode-aware case folding)大小寫。在指定此標誌後,須要經過 CASE_INSENSITIVE 標誌來啓用,不區分大小寫區配將在 Unicode 標準的意義上來完成。默認狀況下,不區分大小寫匹配僅匹配 US-ASCII 字符集中的字符。可摺疊感知 Unicode 大小寫也能經過內嵌標誌表達式(?u)來啓用。指定此標誌可能會對性能會有必定的影響。

Pattern.UNIX_LINES

  啓用 Unix 行模式。在這種模式下,.^$的行爲僅識別「\n」的行結束符。Unix 行模式能夠經過內嵌標誌表達式(?d)來啓用。
  接下來,將修改測試用具 RegexTestHarness.java,用於構建不區分大小寫匹配的模式。
  首先,修改代碼去調用 complie 的另一個備用的方法:

1 Pattern pattern = Pattern.compile( 
2         console.readLine("%nEnter your regex: "), 
3         Pttern.CASE_INSENSITIVE 
4 );

編譯並運行這個測試用具,會得出下面的結果:

1 Enter your regex: dog
2 Enter input string to search: DoGDOg
3 I found the text "DoG" starting at index 0 and ending at index 3.
4 I found the text "DOg" starting at index 3 and ending at index 6.

正如你所看到的,無論是否大小寫,字符串字面上是「dog」的都產生了匹配。使用多個標誌來編譯一個模式,使用按位或操做符「|」分隔各個標誌。爲了更清晰地說明,下面的示例代碼使用硬編碼(hardcode)的方式,來取代控制檯中的讀取:

1 pattern = Pattern.compile("[az]$", Pattern.MULTILINE | Pattern.UNIX_LINES);

也可使用一個 int 類型的變量來代替:

1 final int flags = Pattern.CASE_INSENSITIVE | Pattern.UNICODE_CASE; 
2 Pattern pattern = Pattern.compile("aa", flags);

8.2 內嵌標誌表達式

  使用內嵌標誌表達式(embedded flag expressions)也能夠啓用不一樣的標誌。對於兩個參數的 compile 方法,內嵌標誌表達式是可選的,由於它在自身的正則表達式中被指定了。下面的例子使用最初的測試用具(RegexTestHarness.java),使用內嵌標誌表達式(?i)來啓用不區分大小寫的匹配。

Enter your regex: (?i)foo
Enter input string to search: FOOfooFoOfoO
I found the text "FOO" starting at index 0 and ending at index 3.
I found the text "foo" starting at index 3 and ending at index 6.
I found the text "FoO" starting at index 6 and ending at index 9.
I found the text "foO" starting at index 9 and ending at index 12.

全部匹配無關大小寫都一次次地成功了。
  內嵌標誌表達式所對應 Pattern 的公用的訪問字段表示以下表:

常  量 等價的內嵌標誌表達式
Pattern.CANON_EQ 沒有
Pattern.CASE_INSENSITIVE (?i)
Pattern.COMMENTS (?x)
Pattern.MULTILINE (?m)
Pattern.DOTALL (?s)
Pattern.LITERAL 沒有
Pattern.UNICODE_CASE (?u)
Pattern.UNIX_LINES (?d)
8.3 使用 matches(String, CharSequence) 方法  

  Pattern 類定義了一個方便的 matches 方法,用於快速地檢查模式是否表示給定的輸入字符串。與使用全部的公共靜態方法同樣,應該經過它的類名來調用 matches 方法,諸如 Pattern.matches("\\d","1");。這個例子中,方法返回 true,這是因爲數字「1」匹配了正則表達式\d

8.4 使用 split(String) 方法

split 方法是一個重要的工具,用於收集依賴於被匹配的模式任一邊的文本。以下面的 SplitDemo.java 所示,split 方法能從「one:two:three:four:five」字符串中解析出「one two three four five」單詞:

 1 import java.util.regex.Pattern; 
 2  
 3 public class SplitDemo { 
 4  
 5     private static final String REGEX = ":"; 
 6     private static final String INPUT = "one:two:three:four:five"; 
 7      
 8     public static void main(String[] args) { 
 9         Pattern p = Pattern.compile(REGEX); 
10         String[] items = p.split(INPUT); 
11         for(String s : items) { 
12             System.out.println(s); 
13         } 
14     } 
15 }

輸出:

1 one
2 two
3 three
4 four
5 five

簡而言之,已經使用冒號(:)取代了複雜的正則表達式匹配字符串文字。之後仍會使用 Pattern 和 Matcher 對象,也能使用 split 獲得位於任意正則表達式各邊的文本。下面的 SplitDemo2.java 是個同樣的例子,使用數字做爲 split 的參數:

 1 import java.util.regex.Pattern; 
 2  
 3 public class SplitDemo2 { 
 4  
 5     private static final String REGEX = "\\d"; 
 6     private static final String INPUT = "one9two4three7four1five"; 
 7  
 8     public static void main(String[] args) { 
 9         Pattern p = Pattern.compile(REGEX); 
10         String[] items = p.split(INPUT); 
11         for(String s : items) { 
12             System.out.println(s); 
13         } 
14     } 
15 }

輸出:

one
two
three
four
five

8.5 其餘有用的方法 

  你能夠從下面的方法中找到比較好用的方法:
  [9]:返回指定字符串字面模式的字符串。此方法會產生一個字符串,能被用於構建一個與字符串 s 匹配的 Pattern,好像它是一個字面上的模式。輸入序列中的元字符和轉義序列將沒有特殊的意義了。
  :返回這個模式的字符串表現形式。這是一個編譯過的模式中的正則表達式。

8.6 在 java.lang.String 中等價的 Pattern 方法

  java.lang.String 經過模擬 java.util.regex.Pattern 行爲的幾個方法,也能夠支持正則表達式。方便起見,下面主要摘錄了出如今 API 關鍵的方法。
  :告知字符串是否匹配給定的正則表達式。調用 str.matches(regex)方法所產生的結果與做爲表達式的 Pattern.matches(regex, str)的結果是徹底一致。
  :依照匹配給定的正則表達式來拆分字符串。調用 str.split(regex, n)方法所產生的結果與做爲表達式的 Pattern.compile(regex).split(str, n) 的結果徹底一致。
  :依照匹配給定的正則表達式來拆分字符串。這個方法與調用兩個參數的 split 方法是相同的,第一個參數使用給定的表達式,第二個參數限制爲 0。在結果數組中不包括尾部的空字符串。
  還有一個替換方法,把一個 CharSequence 替換成另一個:
  :將字符串中每個匹配替換匹配字面目標序列的子字符串,替換成指定的字面替換序列。這個替換從字符串的開始處理直至結束,例如,把字符串「aaa」中的「aa」替換成「b」,結果是「ba」,而不是「ab」。

9 Matcher 類的方法

在這一節中來看看 Matcher 類中其餘一些有用的方法。方便起見,下面列出的方法是按照功能來分組的。

索引方法

  索引方法(index methods)提供了一些正好在輸入字符串中發現匹配的索引值:
  :返回以前匹配的開始索引。
  :返回以前匹配操做中經過給定組所捕獲序列的開始索引。
  : 返回最後匹配字符後的偏移量。
  : 返回以前匹配操做中經過給定組所捕獲序列的最後字符以後的偏移量。

研究方法

  研究方法(study methods)回顧輸入的字符串,而且返回一個用於指示是否找到模式的布爾值。
  : 嘗試從區域開頭處開始,輸入序列與該模式匹配。
  : 嘗試地尋找輸入序列中,匹配模式的下一個子序列。
  : 重置匹配器,而後從指定的索引處開始,嘗試地尋找輸入序列中,匹配模式的下一個子序列。
  : 嘗試將整個區域與模式進行匹配

替換方法

  替換方法(replacement methods)用於在輸入的字符串中替換文本有用處的方法。
  :實現非結尾處的增長和替換操做。
  :實現結尾處的增長和替換操做。
  :使用給定的替換字符串來替換輸入序列中匹配模式的每個子序列。
  :使用給定的替換字符串來替換輸入序列中匹配模式的第一個子序列。
  :返回指定字符串的字面值來替換字符串。這個方法會生成一個字符串,用做 Matcher 的 appendReplacement 方法中的字面值替換 s。所產生的字符串將與做爲字面值序列的 s 中的字符序列匹配。斜線(\)和美圓符號($)將再也不有特殊意義了。

9.1 使用 start 和 end 方法 返回目錄

  示例程序 MatcherDemo.java 用於計算輸入序列中單詞「dog」的出現次數。

 1 import java.util.regex.Pattern; 
 2 import java.util.regex.Matcher; 
 3  
 4 public class MatcherDemo { 
 5  
 6     private static final String REGEX = "\\bdog\\b"; 
 7     private static final String INPUT = "dog dog dog doggie dogg"; 
 8  
 9     public static void main(String[] args) { 
10        Pattern p = Pattern.compile(REGEX); 
11        Matcher m = p.matcher(INPUT);        // 得到匹配器對象 
12        int count = 0; 
13        while (m.find()) { 
14            count++; 
15            System.out.println("Match number " + count); 
16            System.out.println("start(): " + m.start()); 
17            System.out.println("end(): " + m.end()); 
18        } 
19     } 
20 }

輸出:

Match number 1
start(): 0
end(): 3
Match number 2
start(): 4
end(): 7
Match number 3
start(): 8
end(): 11

能夠看出,這個例子使用了單詞邊界,用於確保更長單詞中的字母「d」「o」「g」就不是子串了。它也輸出了一些有用的信息,在輸入的字符串中什麼地方有匹配。start 方法返回在之前的匹配操做期間,由給定組所捕獲子序列的開始處索引,end 方法返回匹配到最後一個字符索引加 1。

9.2 使用 matches 和 lookingAt 方法  

  matches 和 lookingAt 方法都是嘗試該模式匹配輸入序列。然而不一樣的是,matches 要求匹配整個輸入字符串,而 lookingAt 不是這樣。這兩個方法都是從輸入字符串的開頭開始的。下面是 MatchesLooking.java 完整的代碼:

 1 import java.util.regex.Pattern; 
 2 import java.util.regex.Matcher; 
 3  
 4 public class MatchesLooking { 
 5  
 6     private static final String REGEX = "foo"; 
 7     private static final String INPUT = "fooooooooooooooooo"; 
 8     private static Pattern pattern; 
 9     private static Matcher matcher; 
10  
11     public static void main(String[] args) { 
12     
13         // 初始化 
14         pattern = Pattern.compile(REGEX); 
15         matcher = pattern.matcher(INPUT); 
16  
17         System.out.println("Current REGEX is: " + REGEX); 
18         System.out.println("Current INPUT is: " + INPUT); 
19  
20         System.out.println("lookingAt(): " + matcher.lookingAt()); 
21         System.out.println("matches(): " + matcher.matches()); 
22     } 
23 }

輸出:

1 Current REGEX is: foo
2 Current INPUT is: fooooooooooooooooo
3 lookingAt(): true
4 matches(): false

9.3 使用 replaceFirst(String) 和 replaceAll(String) 方法 

  replaceFirst 和 replaceAll 方法替換匹配給定正則表達式的文本。從它們的名字能夠看出,replaceFirst 替換第一個匹配到的,而 replaceAll 替換全部匹配的。下面是 ReplaceDemo.java 的代碼:

 1 import java.util.regex.Pattern;  
 2 import java.util.regex.Matcher; 
 3  
 4 public class ReplaceDemo { 
 5   
 6     private static String REGEX = "dog"; 
 7     private static String INPUT = "The dog says meow. All dogs say meow."; 
 8     private static String REPLACE = "cat"; 
 9   
10     public static void main(String[] args) { 
11         Pattern p = Pattern.compile(REGEX); 
12         Matcher m = p.matcher(INPUT);       // 得到匹配器對象 
13         INPUT = m.replaceAll(REPLACE); 
14         System.out.println(INPUT); 
15     } 
16 }

輸出:

1 The cat says meow. All cats say meow.

在上面的例子中,全部的 dog 都被替換成了 cat。可是爲何在這裏停下來了呢?你能夠替換匹配任何正則表達式的文本,這樣優於替換一個簡單的像 dog 同樣的文字。這個方法的 API 描述了「給定正則表達式a*b,在輸入‘aabfooaabfooabfoob’和替換的字符串是‘-’狀況下,表達式的匹配器調用方法後,會產生成字符串‘-foo-foo-foo-’。」
  下面是 ReplaceDemo2.java 的代碼:

 1 import java.util.regex.Pattern; 
 2 import java.util.regex.Matcher; 
 3   
 4 public class ReplaceDemo2 { 
 5   
 6     private static String REGEX = "a*b"; 
 7     private static String INPUT = "aabfooaabfooabfoob"; 
 8     private static String REPLACE = "-"; 
 9   
10     public static void main(String[] args) { 
11         Pattern p = Pattern.compile(REGEX); 
12         Matcher m = p.matcher(INPUT);       // 得到匹配器對象 
13         INPUT = m.replaceAll(REPLACE); 
14         System.out.println(INPUT); 
15     } 
16 }

輸出:

-foo-foo-foo-

僅要替換模式一次時,能夠簡單地調用 replaceFirst 用於取代 replaceAll,它接受相同的參數。

9.4 使用 appendReplacement(StringBuffer, String) 和
      appendTail(StringBuffer) 方法  

  Matcher 類也提供了 appendReplacement 和 appendTail 兩個方法用於文本替換。下面的這個例子(RegexDemo.java)使用了這兩個方法完成與 replaceAll 相同的功能。

 1 import java.util.regex.Pattern; 
 2 import java.util.regex.Matcher; 
 3  
 4 public class RegexDemo { 
 5   
 6     private static String REGEX = "a*b"; 
 7     private static String INPUT = "aabfooaabfooabfoob"; 
 8     private static String REPLACE = "-"; 
 9   
10     public static void main(String[] args) { 
11         Pattern p = Pattern.compile(REGEX); 
12         Matcher m = p.matcher(INPUT);       // 得到匹配器對象 
13         StringBuffer sb = new StringBuffer(); 
14         while (m.find()) { 
15             m.appendReplacement(sb, REPLACE); 
16         } 
17         m.appendTail(sb); 
18         System.out.println(sb.toString()); 
19     } 
20 }

輸出:

-foo-foo-foo-

9.5 在 java.lang.String 中等價的 Matcher 方法

  爲了使用方便,String 類看上去還不錯地模仿了 Matcher 的兩個方法:
  :使用給定的替換字符串替換該字符串中匹配了給定正則表達式的第一個子字符串。調用 str.replaceFirst(regex, repl)方法與使用 Pattern.compile(regex).matcher(str).replaceFirst(repl)產生的結果是徹底相同的。
  :使用給定的替換字符串替換該字符串中匹配了給定正則表達式的每個子字符串。調用 str.replaceAll(regex, repl)方法與使用 Pattern.compile(regex).matcher(str).replaceAll(repl)產生的結果是徹底相同的。

 

10 PatternSyntaxException 類的方法

PatternSyntaxException 是未檢查異常,指示正則表達式模式中的語法錯誤。PatternSyntaxException 類提供了下面的一些方法,用於肯定在什麼地方發生了錯誤:
  :得到錯誤描述。
  :得到錯誤索引。
  :得到字符串形式的錯誤正則表達式。
  :得到一個多行的字符串,包括語法錯誤和錯誤的索引、錯誤的正則表達式模式,以及模式內可視化的索引指示。
  下面的源代碼(RegexTestHarness2.java[10])更新了測試用具,用於檢查不正確的正則表達式:

 1 import java.io.Console; 
 2 import java.util.regex.Pattern; 
 3 import java.util.regex.Matcher; 
 4 import java.util.regex.PatternSyntaxException; 
 5  
 6 public class RegexTestHarness2 { 
 7  
 8     public static void main(String[] args){ 
 9         Pattern pattern = null; 
10         Matcher matcher = null; 
11  
12         Console console = System.console(); 
13         if (console == null) { 
14             System.err.println("No console."); 
15             System.exit(1); 
16         } 
17         while (true) { 
18             try { 
19                 pattern = Pattern.compile(console.readLine("%nEnter your regex: ")); 
20                 matcher = pattern.matcher(console.readLine("Enter input string to search: ")); 
21             } catch (PatternSyntaxException pse){ 
22                 console.format("There is a problem with the regular expression!%n"); 
23                 console.format("The pattern in question is: %s%n", pse.getPattern()); 
24                 console.format("The description is: %s%n", pse.getDescription()); 
25                 console.format("The message is: %s%n", pse.getMessage()); 
26                 console.format("The index is: %s%n", pse.getIndex()); 
27                 System.exit(0); 
28             } 
29             boolean found = false; 
30             while (matcher.find()) { 
31                 console.format("I found the text \"%s\" starting at " + 
32                         "index %d and ending at index %d.%n", 
33                         matcher.group(), matcher.start(), matcher.end() 
34                     ); 
35                 found = true; 
36             } 
37             if (!found){ 
38                 console.format("No match found.%n"); 
39             } 
40         } 
41     } 
42 }

運行該測試,輸入?i)foo做爲正則表達式。這是個臆想出來的錯誤,程序員在使用內嵌標誌表達式(?i)時忘記輸入左括號了。這樣作會產生下面的結果:

Enter your regex: ?i)
There is a problem with the regular expression!
The pattern in question is: ?i)
The description is: Dangling meta character '?'
The message is: Dangling meta character '?' near index 0
?i)
^
The index is: 0

從這個輸出中,能夠看出在索引 0 處的元字符(?)附近有語法錯誤。缺乏左括號是致使這個錯誤的最魁禍首。

11 更多的資源

如今已經結束了正則表達式的課程,你也許會發現,主要引用了 Pattern、Matcher 和 PatternSyntaxException 類的 API 文檔。
  構建正則表達式更詳細地描述,推薦閱讀 Jeffrey E.F.Friedl 的Mastering Regular Expressions[11]

12 問題和練習

〖問題〗1. 在 java.util.regex 包中有哪三個公共的類?描述一下它們的做用。
2. 考慮一下字符串「foo」,它的開始索引是多少?結束索引是多少?解釋一下這些編號的意思。
3. 普通字符和元字符有什麼不一樣?各給出它們的一個例子。
4. 如何把元字符表現成像普通字符那樣?
5. 附有方括號的字符集稱爲何?它有什麼做用?
6. 這裏是三個預約義的字符類:\d\s\w。描述一下它們各表示什麼?並使用方括號的形式將它們重寫。
7. 對於\d\s\w,寫出兩個簡單的表達式,匹配它們相反的字符集。
8. 思考正則表達式(dog){3},識別一下其中的兩個子表達式。這個表達式會匹配什麼字符串?

〖練習〗

1. 使用反向引用寫一個表達式,用於匹配一我的的名字,假設這我的的 first 名字與 last 名字是相同的。

【問題答案】

1. 在 java.util.regex 包中有哪三個公共的類?描述一下它們的做用。

  • 編譯後的 Pattern 實例表示正則表達式。
  • Matcher 實例是解析模式和靠着輸入的字符串完成匹配操做的引擎。
  • PatternSyntaxException 定義一個未檢查異常,指示正則表達式中的語法錯誤。

2. 考慮一下字符串「foo」,它的開始索引是多少?結束索引是多少?解釋一下這些編號的意思。

字符串中的每個字符位於其自身的單元格中。索引位置在兩個單元格之間。字符串「foo」開始於索引 0,結束於索引 3,即使是這些字符僅佔用了 0、1 和 2 號單元格。

3. 普通字符和元字符有什麼不一樣?各給出它們的一個例子。

正則表達式中的普通字符匹配其自己。元字符是一個特殊的字符,會影響被匹配模式的方式。字母 A是一個普通字符。標點符號 .是一個元字符,其匹配任意的單字符。

4. 如何把元字符表現成像普通字符那樣?

有兩種方法:
  • 在元字符前加上反斜線(\);
  • 把元字符置於\Q(開始)\E(結束)的引用表達式中。

5. 附有方括號的字符集稱爲何?它有什麼做用?

是一個字符類。經過方括號間的表達式,匹配指定字符類中的任意一個字符。

6. 這裏是三個預約義的字符類:\d\s\w。描述一下它們各表示什麼?並使用方括號的形式將它們重寫。

\d 匹配任意數字 [0-9]
   \s 匹配任意空白字符 [ \t\n-x0B\f\r]
   \w 匹配任意單詞字符 [a-zA-Z_0-9]

7. 對於\d\s\w,寫出兩個簡單的表達式,匹配它們相反的字符集。

\d  \D  [^\d]
   \s  \S  [^\s]
   \w  \W  [^\w]

8. 思考正則表達式(dog){3},識別一下其中的兩個子表達式。這個表達式會匹配什麼字符串?

表達式由捕獲組 (dog)和接着的貪婪量詞 {3}所組成。它匹配字符串「dogdogdog」。
【練習答案】

1. 使用反向引用寫一個表達式,用於匹配一我的的名字,假設這我的的 first 名字與 last 名字是相同的。

([A-Z][a-zA-Z]*)\s\1
 
註釋

[1]  本文全文譯自 Java Tutorial 的 Regular Expressions,標題是譯者自擬的。——譯者注

[2]  Unix 工具,用於文件中的字符串查找,它是最先的正則表達式工具之一。——譯者注

[3]  若要退出可使用 Ctrl + C 來中斷。——譯者注

[4]  圖中的「索引 3」指示是譯者所加,原文中並無。——譯者注

[5]  這種方式在 JDK 6.0 之前版本使用須要注意,在字符類中使用這種結構是有 bug 的,不過在 JDK 6.0 中已經修正。——譯者注

[6]  若\E前沒有\Q時會產生 PatternSyntaxException 異常指示語法錯誤。——譯者注

[7]  第一次匹配時僅匹配字符串的開始部分,與\A相似。(引自 Jeffrey E.F.Friedl, Mastering Regular Expressions, 3rd ed., §3.5.3.3, O'Reilly, 2006.)——譯者注

[8]  \u030A,即字符 å 上半部分的小圓圈( ̊ )(該字符在 IE 瀏覽器上沒法正確顯示,在 Firefox 瀏覽器上能夠正常地顯示)。——譯者注

[9]  JDK 5.0 新增的方法,JDK 1.4 中不能使用。——譯者注

[10]  JDK 1.4 和 JDK 5.0 適用的版本在所附的源代碼中。適用於 JDK 1.4 的文件名爲 RegexTestHarness2V4.java,JDK 1.5 的文件名爲 RegexTestHarness2V5.java。——譯者注

[11]  第三版是本書的最新版本。第三版的中譯本《精通正則表達式》已由電子工業出版社於 2007 年 7 月出版。——譯者注

 

譯後記

帶着忐忑不安的心情完成了個人第一篇譯篇,希望這個教程能讓你們對 Java 中的正則表達式有更一步的認識。
  雖然這是一個關於 Java 正則表達式很好的一個入門教程,但這個教程也有其不足之處,其中僅僅涉及了最爲簡單的正則表達式,對介紹到的有些問題並未徹底展開,好比:字符類中的轉義、內嵌標誌表達式具體的用法等。對有些經常使用的表達式,如|(選擇結構)也沒有涉及。對於非捕獲組來講,僅僅提到了內嵌標誌表達式,對於諸如(?:X)(?=X)(?!X)(?<=X)(?<!X)(?>X)等等之類的非捕獲組結構徹底沒有涉及。正如譯者在序中提到的,這篇文章只爲從此學習更高級的正則表達式技術奠基良好的基礎。

譯者 2008 年 3 月 2 日
相關文章
相關標籤/搜索