Java正則表達式教程

 Java正則表達式教程 [1]
Regular Expressions of Java Tutorial

譯者序

  正則表達式善於處理文本,對匹配、搜索和替換等操作都有意想不到的作用。正因如此,正則表達式現在是作爲程序員七種基本技能之一*,因此學習和使用它在工作中都能達到很高的效率。
  正則表達式應用於程序設計語言中,首次是出現在 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 或以上版本,沒有這些後綴的類在各個版本中均可以正常使用。
  由於譯者的水平和技術能力有限,譯稿雖經多次校對,難免有疏漏之處,敬請大家批評和指正。若有發現不妥之處,請發送郵件至 [email protected],我會在 blog 中進行勘誤,謝謝!

    火龍果頓首!

2008 2 27




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

目錄

·      返回目錄

·        本文介紹如何使用 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]PerlTclPythonPHP awkjava.util.regex API 中的正則表達式語法與 Perl 中的最爲相似。

0.2 java.util.regex 包是如何描述正則表達式的?返回目錄

  java.util.regex 包主要由三個類所組成:PatternMatcher PatternSyntaxException

·      Pattern 對象表示一個已編譯的正則表達式。Pattern 類沒有提供公共的構造方法。要構建一個模式,首先必須調用公共的靜態 compile 方法,它將返回一個 Pattern 對象。這個方法接受正則表達式作爲第一個參數。本教程的開始部分將教你必需的語法。

·      Matcher 是一個靠着輸入的字符串來解析這個模式和完成匹配操作的對象。與 Pattern 相似,Matcher 也沒有定義公共的構造方法,需要通過調用 Pattern 對象的 matcher 方法來獲得一個 Matcher 對象。

·      PatternSyntaxException 對象是一個未檢查異常,指示了正則表達式中的一個語法錯誤。

·           本教程的最後幾節課程會詳細地說明各個類。首當其衝的問題是:必須理解正則表達式是如何被構建的,因此下一節引入了一個簡單的測試用具,重複地用於探究它們的語法。

·      1 測試用具返回目錄

·        這節給出了一個可重用的測試用具 RegexTestHarness.java,用於探究構建 API 所支持的正則表達式。使用

·         java RegexTestHarness

·      這個命令來運行,沒有被接受的命令行參數。這個應用會不停地循環執行下去[3],提示用戶輸入正則表達式和字符串。雖然說使用這個測試用具是可選的,但你會發現它用於探究下文所討論的測試用例將更爲方便。

·         import java.io.Console;  
import
java.util.regex.Pattern;  
import
java.util.regex.Matcher;  
 
public class
RegexTestHarness {  
 
    
public static void
main(String[] args) {  
        Console console = System.
console
();  
        
if (console == null
) {  
            System.
err.println("No console."
);  
            System.
exit
(1);  
        }  
        
        
while (true
) {  
            Pattern pattern = Pattern.
compile(console.readLine("%nEnter your regex: "
));  
            Matcher matcher = pattern.matcher(console.readLine(
"Enter input string to search: "
));  
            
boolean found = false
;  
            
while
(matcher.find()) {  
                console.format(
"I found the text /"%s/" starting at index %d "
+  
                        
"and ending at index %d.%n"
,  
                        matcher.group(), matcher.start(), matcher.end());  
                found =
true
;  
            }  
            
if
(!found) {  
                console.format(
"No match found.%n"
);  
            }  
        }  
    }  
}

·        在繼續下一節之前,確認開發環境支持必需的包,並保存和編譯這段代碼。

·      【譯者注】

·        由於當前版本的 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 環境也採用該用具。

·         import java.util.Scanner;  
import
java.util.regex.Matcher;  
import
java.util.regex.Pattern;  
 
public class
RegexTestHarnessV5 {  
 
    
public static void
main(String[] args) {  
        Scanner scanner =
new Scanner(System.in
);  
        
while (true
) {  
            System.
out.printf("%nEnter your regex: "
);  
            Pattern pattern = Pattern.
compile
(scanner.nextLine());  
            System.
out.printf("Enter input string to search: "
);  
            Matcher matcher = pattern.matcher(scanner.nextLine());  
            
boolean found = false
;  
            
while
(matcher.find()) {  
                System.
out
.printf(  
                        
"I found the text /"%s/" starting at index %d and ending at index %d.%n"
,  
                        matcher.group(), matcher.start(), matcher.end()  
                    );  
                found =
true
;  
            }  
            
if
(!found) {  
                System.
out.printf("No match found.%n"
);  
            }  
        }  
    }  
}

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

·         import java.io.BufferedInputStream;  
import
java.io.BufferedReader;  
import
java.io.IOException;  
import
java.io.InputStreamReader;  
import
java.util.regex.Matcher;  
import
java.util.regex.Pattern;  
 
public class
RegexTestHarnessV4 {  
 
    
public static void main(String[] args) throws
IOException {  
        BufferedReader br =
new
BufferedReader(  
                
new InputStreamReader(new BufferedInputStream(System.in
))  
            );  
        
while (true
) {  
            System.
out.print("/nEnter your regex: "
);  
            Pattern pattern = Pattern.
compile
(br.readLine());  
            System.
out.print("Enter input string to search: "
);  
            Matcher matcher = pattern.matcher(br.readLine());  
            
boolean found = false
;  
            
while
(matcher.find()) {  
                System.
out.println("I found the text /""
+ matcher.group() +  
                        
"/" starting at index "
+ matcher.start() +  
                        
" and ending at index "
+ matcher.end() +  
                        
"."
);  
                found =
true
;  
            }  
            
if
(!found) {  
                System.
out.println("No match found."
);  
            }  
        }  
    }  
}

 

2 字符串返回目錄

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

Enter your regex: foo

Enter input string to search: foo

I found the text "foo" starting at index 0 and ending at index 3.

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


1 字符串“foo”的單元格編號和索引值[4]

  字符串中的每一個字符位於其自身的單元格cell)中,在每個單元格之間有索引指示位。字符串“foo”始於索引 0 處,止於索引 3 處,即使是這些字符它們自己僅佔據了 01 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”,輸出如下所示:

Enter your regex: cat.

Enter input string to search: cats

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]]

de 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”中的一個字符)作爲它的首字符。

 

Enter your regex: [bcr]at

Enter input string to search: bat

I found the text "bat" starting at index 0 and ending at index 3.

 

Enter your regex: [bcr]at

Enter input string to search: cat

I found the text "cat" starting at index 0 and ending at index 3.

 

Enter your regex: [bcr]at

Enter input string to search: rat

I found the text "rat" starting at index 0 and ending at index 3.

 

Enter your regex: [bcr]at

Enter input string to search: hat

No match found.

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

3.1.1 否定返回目錄

  要匹配除那些列表之外所有的字符時,可以在字符類的開始處加上^元字符,這種就被稱爲否定negation)。

Enter your regex: [^bcr]at

Enter input string to search: bat

No match found.

 

Enter your regex: [^bcr]at

Enter input string to search: cat

No match found.

 

Enter your regex: [^bcr]at

Enter input string to search: rat

No match found.

 

Enter your regex: [^bcr]at

Enter input string to search: hat

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(大寫字母)中的任何一個字符。
  下面是一些範圍和否定的例子:

Enter your regex: [a-c]

Enter input string to search: a

I found the text "a" starting at index 0 and ending at index 1.

 

Enter your regex: [a-c]

Enter input string to search: b

I found the text "b" starting at index 0 and ending at index 1.

 

Enter your regex: [a-c]

Enter input string to search: c

I found the text "c" starting at index 0 and ending at index 1.

 

Enter your regex: [a-c]

Enter input string to search: d

No match found.

 

Enter your regex: foo[1-5]

Enter input string to search: foo1

I found the text "foo1" starting at index 0 and ending at index 4.

 

Enter your regex: foo[1-5]

Enter input string to search: foo5

I found the text "foo5" starting at index 0 and ending at index 4.

 

Enter your regex: foo[1-5]

Enter input string to search: foo6

No match found.

 

Enter your regex: foo[^1-5]

Enter input string to search: foo1

No match found.

 

Enter your regex: foo[^1-5]

Enter input string to search: foo6

I found the text "foo6" starting at index 0 and ending at index 4.

3.1.3 並集返回目錄

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

Enter your regex: [0-4[6-8]]

Enter input string to search: 0

I found the text "0" starting at index 0 and ending at index 1.

 

Enter your regex: [0-4[6-8]]

Enter input string to search: 5

No match found.

 

Enter your regex: [0-4[6-8]]

Enter input string to search: 6

I found the text "6" starting at index 0 and ending at index 1.

 

Enter your regex: [0-4[6-8]]

Enter input string to search: 8

I found the text "8" starting at index 0 and ending at index 1.

 

Enter your regex: [0-4[6-8]]

Enter input string to search: 9

No match found.

3.1.4 交集返回目錄

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

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.

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

Enter your regex: [2-8&&[4-6]]

Enter input string to search: 3

No match found.

 

Enter your regex: [2-8&&[4-6]]

Enter input string to search: 4

I found the text "4" starting at index 0 and ending at index 1.

 

Enter your regex: [2-8&&[4-6]]

Enter input string to search: 5

I found the text "5" starting at index 0 and ending at index 1.

 

Enter your regex: [2-8&&[4-6]]

Enter input string to search: 6

I found the text "6" starting at index 0 and ending at index 1.

 

Enter your regex: [2-8&&[4-6]]

Enter input string to search: 7

No match found.

3.1.5 差集返回目錄

  最後,可以使用差集subtraction)來否定一個或多個嵌套的字符類,比如:[0-9&&[^345]],這個是構建一個匹配除 345 之外所有 0 9 間數字的簡單字符類。

Enter your regex: [0-9&&[^345]]

Enter input string to search: 2

I found the text "2" starting at index 0 and ending at index 1.

 

Enter your regex: [0-9&&[^345]]

Enter input string to search: 3

No match found.

 

Enter your regex: [0-9&&[^345]]

Enter input string to search: 4

No match found.

 

Enter your regex: [0-9&&[^345]]

Enter input string to search: 5

No match found.

 

Enter your regex: [0-9&&[^345]]

Enter input string to search: 6

I found the text "6" starting at index 0 and ending at index 1.

 

Enter your regex: [0-9&&[^345]]

Enter input string to search: 9

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指的是數字範圍(09),/w指的是單詞字符(任何大小寫字母、下劃線或者是數字)。無論何時都有可能使用預定義字符類,它可以使代碼更易閱讀,更易從難看的字符類中排除錯誤。
  以反斜線(/)開始的構造稱爲轉義構造escaped constructs)。回顧一下在 字符串 一節中的轉義構造,在那裏我們提及了使用反斜線,以及用於引用的/Q/E。在字符串中使用轉義構造,必須在一個反斜線前再增加一個反斜用於字符串的編譯,例如:

001

private final String REGEX = "//d";        // 單個數字

  這個例子中/d是正則表達式,另外的那個反斜線是用於代碼編譯所必需的。但是測試用具讀取的表達式,是直接從控制檯中輸入的,因此不需要那個多出來的反斜線。
  下面的例子說明了預字義字符類的用法:

Enter your regex: .

Enter input string to search: @

I found the text "@" starting at index 0 and ending at index 1.

 

Enter your regex: .

Enter input string to search: 1

I found the text "1" starting at index 0 and ending at index 1.

 

Enter your regex: .

Enter input string to search: a

I found the text "a" starting at index 0 and ending at index 1.

 

Enter your regex: /d

Enter input string to search: 1

I found the text "1" starting at index 0 and ending at index 1.

 

Enter your regex: /d

Enter input string to search: a

No match found.

 

Enter your regex: /D

Enter input string to search: 1

No match found.

 

Enter your regex: /D

Enter input string to search: a

I found the text "a" starting at index 0 and ending at index 1.

 

Enter your regex: /s

Enter input string to search: 

I found the text " " starting at index 0 and ending at index 1.

 

Enter your regex: /s

Enter input string to search: a

No match found.

 

Enter your regex: /S

Enter input string to search: 

No match found.

 

Enter your regex: /S

Enter input string to search: a

I found the text "a" starting at index 0 and ending at index 1.

 

Enter your regex: /w

Enter input string to search: a

I found the text "a" starting at index 0 and ending at index 1.

 

Enter your regex: /w

Enter input string to search: !

No match found.

 

Enter your regex: /W

Enter input string to search: a

No match found.

 

Enter your regex: /W

Enter input string to search: !

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後面跟着?*+。接下來看一下,用這些表達式來測試輸入的字符串是空字符串時會發生些什麼:

Enter your regex: a?

Enter input string to search:

I found the text "" starting at index 0 and ending at index 0.

 

Enter your regex: a*

Enter input string to search:

I found the text "" starting at index 0 and ending at index 0.

 

Enter your regex: a+

Enter input string to search:

No match found.

5.1 零長度匹配返回目錄

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

Enter your regex: a?

Enter input string to search: a

I found the text "a" starting at index 0 and ending at index 1.

I found the text "" starting at index 1 and ending at index 1.

 

Enter your regex: a*

Enter input string to search: a

I found the text "a" starting at index 0 and ending at index 1.

I found the text "" starting at index 1 and ending at index 1.

 

Enter your regex: a+

Enter input string to search: a

I found the text "a" starting at index 0 and ending at index 1.

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

Enter your regex: a?

Enter input string to search: aaaaa

I found the text "a" starting at index 0 and ending at index 1.

I found the text "a" starting at index 1 and ending at index 2.

I found the text "a" starting at index 2 and ending at index 3.

I found the text "a" starting at index 3 and ending at index 4.

I found the text "a" starting at index 4 and ending at index 5.

I found the text "" starting at index 5 and ending at index 5.

 

Enter your regex: a*

Enter input string to search: aaaaa

I found the text "aaaaa" starting at index 0 and ending at index 5.

I found the text "" starting at index 5 and ending at index 5.

 

Enter your regex: a+

Enter input string to search: aaaaa

I found the text "aaaaa" starting at index 0 and ending at index 5.

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

Enter your regex: a?

Enter input string to search: ababaaaab

I found the text "a" starting at index 0 and ending at index 1.

I found the text "" starting at index 1 and ending at index 1.

I found the text "a" starting at index 2 and ending at index 3.

I found the text "" starting at index 3 and ending at index 3.

I found the text "a" starting at index 4 and ending at index 5.

I found the text "a" starting at index 5 and ending at index 6.

I found the text "a" starting at index 6 and ending at index 7.

I found the text "a" starting at index 7 and ending at index 8.

I found the text "" starting at index 8 and ending at index 8.

I found the text "" starting at index 9 and ending at index 9.

 

Enter your regex: a*

Enter input string to search: ababaaaab

I found the text "a" starting at index 0 and ending at index 1.

I found the text "" starting at index 1 and ending at index 1.

I found the text "a" starting at index 2 and ending at index 3.

I found the text "" starting at index 3 and ending at index 3.

I found the text "aaaa" starting at index 4 and ending at index 8.

I found the text "" starting at index 8 and ending at index 8.

I found the text "" starting at index 9 and ending at index 9.

 

Enter your regex: a+

Enter input string to search: ababaaaab

I found the text "a" starting at index 0 and ending at index 1.

I found the text "a" starting at index 2 and ending at index 3.

I found the text "aaaa" starting at index 4 and ending at index 8.

  即使字母“b”在單元格 138 中出現,但在這些位置上的輸出報告了零長度匹配。正則表達式a?不是特意地去尋找字母“b”,它僅僅是去找字母“a”存在或者其中缺少的。如果量詞允許匹配“a”零次,任何輸入的字符不是“a”時將會作爲零長度匹配。在前面的例子中,根據討論的規則保證了 a 被匹配。
  對於要精確地匹配一個模式 n 次時,可以簡單地在一對花括號內指定一個數值:

Enter your regex: a{3}

Enter input string to search: aa

No match found.

 

Enter your regex: a{3}

Enter input string to search: aaa

I found the text "aaa" starting at index 0 and ending at index 3.

 

Enter your regex: a{3}

Enter input string to search: aaaa

I found the text "aaa" starting at index 0 and ending at index 3.

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

Enter your regex: a{3}

Enter input string to search: aaaaaaaaa

I found the text "aaa" starting at index 0 and ending at index 3.

I found the text "aaa" starting at index 3 and ending at index 6.

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”的要求。
  最後,對於指定出現次數的上限,可以在花括號添加第二個數字。

Enter your regex: a{3,6} // 尋找一行中至少連續出現 3 個(但不多於 6 個)“a”

Enter input string to search: aaaaaaaaa

I found the text "aaaaaa" starting at index 0 and ending at index 6.

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”上,從而導致這個匹配失敗。
  類似地,也能把量詞應用於整個字符類:

Enter your regex: [abc]{3}

Enter input string to search: abccabaaaccbbbc

I found the text "abc" starting at index 0 and ending at index 3.

I found the text "cab" starting at index 3 and ending at index 6.

I found the text "aaa" starting at index 6 and ending at index 9.

I found the text "ccb" starting at index 9 and ending at index 12.

I found the text "bbc" starting at index 12 and ending at index 15.

 

Enter your regex: abc{3}

Enter input string to search: abccabaaaccbbbc

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 類型的特定組號作爲參數,因此理解組是如何編號的是尤爲重要的。
  public int start(int group):返回之前的匹配操作期間,給定組所捕獲的子序列的初始索引。
  public int end(int group):返回之前的匹配操作期間,給定組所捕獲子序列的最後字符索引加 1
  public String group (int group):返回之前的匹配操作期間,通過給定組而捕獲的輸入子序列。

6.2 反向引用返回目錄

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

Enter your regex: (/d/d)/1

Enter input string to search: 1212

I found the text "1212" starting at index 0 and ending at index 4.

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

Enter your regex: (/d/d)/1

Enter input string to search: 1234

No match found.

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

7 邊界匹配器返回目錄

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

邊界匹配器

^

行首

$

行尾

/b

單詞邊界

/B

非單詞邊界

/A

輸入的開頭

/G

上一個匹配的結尾

/Z

輸入的結尾,僅用於最後的結束符(如果有的話)

/z

輸入的結尾

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

Enter your regex: ^dog$

Enter input string to search: dog

I found the text "dog" starting at index 0 and ending at index 3.

 

Enter your regex: ^dog$

Enter input string to search:       dog

No match found.

 

Enter your regex: /s*dog$

Enter input string to search:             dog

I found the text "            dog" starting at index 0 and ending at index 15.

 

Enter your regex: ^dog/w*

Enter input string to search: dogblahblah

I found the text "dogblahblah" starting at index 0 and ending at index 11.

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

Enter your regex: /bdog/b

Enter input string to search: The dog plays in the yard.

I found the text "dog" starting at index 4 and ending at index 7.

 

Enter your regex: /bdog/b

Enter input string to search: The doggie plays in the yard.

No match found.

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

Enter your regex: /bdog/B

Enter input string to search: The dog plays in the yard.

No match found.

 

Enter your regex: /bdog/B

Enter input string to search: The doggie plays in the yard.

I found the text "dog" starting at index 4 and ending at index 7.

  對於需要匹配僅出現在前一個匹配的結尾,可以使用/G

Enter your regex: dog

Enter input string to search: dog dog

I found the text "dog" starting at index 0 and ending at index 3.

I found the text "dog" starting at index 4 and ending at index 7.

 

Enter your regex: /Gdog

Enter input string to search: dog dog

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

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

Pattern.UNIX_LINES

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

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

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

Enter your regex: dog

Enter input string to search: DoGDOg

I found the text "DoG" starting at index 0 and ending at index 3.

I found the text "DOg" starting at index 3 and ending at index 6.

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

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

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

final int flags = Pattern.CASE_INSENSITIVE | Pattern.UNICODE_CASE;  
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”單詞:

import java.util.regex.Pattern;  
 
public class
SplitDemo {  
 
    
private static final String REGEX = ":"
;  
    
private static final String INPUT = "one:two:three:four:five"
;  
      
    
public static void
main(String[] args) {  
        Pattern p = Pattern.
compile(REGEX
);  
        String[] items = p.split(
INPUT
);  
        
for
(String s : items) {  
            System.
out
.println(s);  
        }  
    }  
}

  輸出:

one

two

three

four

five

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

import java.util.regex.Pattern;  
 
public class
SplitDemo2 {  
 
    
private static final String REGEX = "//d"
;  
    
private static final String INPUT = "one9two4three7four1five"
;  
 
    
public static void
main(String[] args) {  
        Pattern p = Pattern.
compile(REGEX
);  
        String[] items = p.split(
INPUT
);  
        
for
(String s : items) {  
            System.
out
.println(s);  
        }  
    }  
}

  輸出:

one

two

three

four

five

8.5 其他有用的方法返回目錄

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

8.6 在 java.lang.String 中等價的 Pattern 方法返回目錄

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

9 Matcher 類的方法返回目錄

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

索引方法

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

研究方法

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

替換方法

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

9.1 使用 start end 方法返回目錄

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

import java.util.regex.Pattern;  
import
java.util.regex.Matcher;  
 
public class
MatcherDemo {  
 
    
private static final String REGEX = "//bdog//b"
;  
    
private static final String INPUT = "dog dog dog doggie dogg"
;  
 
    
public static void
main(String[] args) {  
       Pattern p = Pattern.
compile(REGEX
);  
       Matcher m = p.matcher(
INPUT);        //
獲得匹配器對象  
      
int
count = 0;  
      
while
(m.find()) {  
           count++;  
           System.
out.println("Match number "
+ count);  
           System.
out.println("start(): "
+ m.start());  
           System.
out.println("end(): "
+ m.end());  
       }  
    }  
}

  輸出:

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 完整的代碼:

import java.util.regex.Pattern;  
import
java.util.regex.Matcher;  
 
public class
MatchesLooking {  
 
    
private static final String REGEX = "foo"
;  
    
private static final String INPUT = "fooooooooooooooooo"
;  
    
private static
Pattern pattern;  
    
private static
Matcher matcher;  
 
    
public static void
main(String[] args) {  
    
        
//
初始化  
        pattern = Pattern.
compile(REGEX
);  
        matcher = pattern.matcher(
INPUT
);  
 
        System.
out.println("Current REGEX is: " + REGEX
);  
        System.
out.println("Current INPUT is: " + INPUT
);  
 
        System.
out.println("lookingAt(): "
+ matcher.lookingAt());  
        System.
out.println("matches(): "
+ matcher.matches());  
    }  
}

  輸出:

Current REGEX is: foo

Current INPUT is: fooooooooooooooooo

lookingAt(): true

matches(): false

9.3 使用 replaceFirst(String) replaceAll(String) 方法返回目錄

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

import java.util.regex.Pattern;  
import
java.util.regex.Matcher;  
 
public class
ReplaceDemo {  
   
    
private static String REGEX = "dog"
;  
    
private static String INPUT = "The dog says meow. All dogs say meow."
;  
    
private static String REPLACE = "cat"
;  
   
    
public static void
main(String[] args) {  
        Pattern p = Pattern.
compile(REGEX
);  
        Matcher m = p.matcher(
INPUT);       //
獲得匹配器對象  
        
INPUT = m.replaceAll(REPLACE
);  
        System.
out.println(INPUT
);  
    }  
}

  輸出:

The cat says meow. All cats say meow.

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

import java.util.regex.Pattern;  
import
java.util.regex.Matcher;  
   
public class
ReplaceDemo2 {  
   
    
private static String REGEX = "a*b"
;  
    
private static String INPUT = "aabfooaabfooabfoob"
;  
    
private static String REPLACE = "-"
;  
   
    
public static void
main(String[] args) {  
        Pattern p = Pattern.
compile(REGEX
);  
        Matcher m = p.matcher(
INPUT);       //
獲得匹配器對象  
        
INPUT = m.replaceAll(REPLACE
);  
        System.
out.println(INPUT
);  
    }  
}

  輸出:

-foo-foo-foo-

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

9.4 使用 appendReplacement(StringBuffer, String)
      appendTail(StringBuffer) 方法回目錄

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

import java.util.regex.Pattern;  
import
java.util.regex.Matcher;  
 
public class
RegexDemo {  
   
    
private static String REGEX = "a*b"
;  
    
private static String INPUT = "aabfooaabfooabfoob"
;  
    
private static String REPLACE = "-"
;  
   
    
public static void
main(String[] args) {  
        Pattern p = Pattern.
compile(REGEX
);  
        Matcher m = p.matcher(
INPUT);       //
獲得匹配器對象  
        StringBuffer sb =
new
StringBuffer();  
        
while
(m.find()) {  
            m.appendReplacement(sb,
REPLACE
);  
        }  
        m.appendTail(sb);  
        System.
out
.println(sb.toString());  
    }  
}

  輸出:

-foo-foo-foo-

9.5 在 java.lang.String 中等價的 Matcher 方法返回目錄

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

 

 

10 PatternSyntaxException 類的方法返回目錄

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

import java.io.Console;  
import
java.util.regex.Pattern;  
import
java.util.regex.Matcher;  
import
java.util.regex.PatternSyntaxException;  
 
public class
RegexTestHarness2 {  
 
    
public static void
main(String[] args){  
        Pattern pattern =
null
;  
        Matcher matcher =
null
;  
 
        Console console = System.
console
();  
        
if (console == null
) {  
            System.
err.println("No console."
);  
            System.
exit
(1);  
        }  
        
while (true
) {  
            
try
{  
                pattern = Pattern.
compile(console.readLine("%nEnter your regex: "
));  
                matcher = pattern.matcher(console.readLine(
"Enter input string to search: "
));  
            }
catch
(PatternSyntaxException pse){  
                console.format(
"There is a problem with the regular expression!%n"
);  
                console.format(
"The pattern in question is: %s%n"
, pse.getPattern());  
                console.format(
"The description is: %s%n"
, pse.getDescription());  
                console.format(
"The message is: %s%n"
, pse.getMessage());  
                console.format(
"The index is: %s%n"
, pse.getIndex());  
                System.
exit
(0);  
            }  
            
boolean found = false
;  
            
while
(matcher.find()) {  
                console.format(
"I found the text /"%s/" starting at "
+  
                        
"index %d and ending at index %d.%n"
,  
                        matcher.group(), matcher.start(), matcher.end()  
                    );  
                found =
true
;  
            }  
            
if
(!found){  
                console.format(
"No match found.%n"
);  
            }  
        }  
    }  
}

  運行該測試,輸入?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 更多的資源返回目錄

  現在已經結束了正則表達式的課程,你也許會發現,主要引用了 PatternMatcher 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,即便是這些字符僅佔用了 01 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.javaJDK 1.5 的文件名爲 RegexTestHarness2V5.java——譯者注

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

譯後記返回目錄

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


譯者
2008
3 2

 

發表評論
所有評論
還沒有人評論,想成為第一個評論的人麼? 請在上方評論欄輸入並且點擊發布.
相關文章