Java 正規表示式詳解

嚴亮發表於2016-05-03

Java 提供了功能強大的正規表示式API,在java.util.regex 包下。本教程介紹如何使用正規表示式API。

正規表示式

一個正規表示式是一個用於文字搜尋的文字模式。換句話說,在文字中搜尋出現的模式。例如,你可以用正規表示式搜尋網頁中的郵箱地址或超連結。

正規表示式示例

下面是一個簡單的Java正規表示式的例子,用於在文字中搜尋 http://

String text    =
        "This is the text to be searched " +
        "for occurrences of the http:// pattern.";
String pattern = ".*http://.*";
boolean matches = Pattern.matches(pattern, text);
System.out.println("matches = " + matches);

示例程式碼實際上沒有檢測找到的 http:// 是否是一個合法超連結的一部分,如包含域名和字尾(.com,.net 等等)。程式碼只是簡單的查詢字串 http:// 是否出現。

Java6 中關於正規表示式的API

本教程介紹了Java6 中關於正規表示式的API。

Pattern (java.util.regex.Pattern)

類 java.util.regex.Pattern 簡稱 Pattern, 是Java正規表示式API中的主要入口,無論何時,需要使用正規表示式,從Pattern 類開始

Pattern.matches()

檢查一個正規表示式的模式是否匹配一段文字的最直接方法是呼叫靜態方法Pattern.matches(),示例如下:

String text    =
        "This is the text to be searched " +
        "for occurrences of the pattern.";
String pattern = ".*is.*";
boolean matches = Pattern.matches(pattern, text);
System.out.println("matches = " + matches);

上面程式碼在變數 text 中查詢單詞 “is” 是否出現,允許”is” 前後包含 0或多個字元(由 .* 指定)

Pattern.matches() 方法適用於檢查 一個模式在一個文字中出現一次的情況,或適用於Pattern類的預設設定。

如果需要匹配多次出現,甚至輸出不同的匹配文字,或者只是需要非預設設定。需要通過Pattern.compile() 方法得到一個Pattern 例項。

Pattern.compile()

如果需要匹配一個正規表示式在文字中多次出現,需要通過Pattern.compile() 方法建立一個Pattern物件。示例如下

String text    =
        "This is the text to be searched " +
        "for occurrences of the http:// pattern.";
String patternString = ".*http://.*";
Pattern pattern = Pattern.compile(patternString);

可以在Compile 方法中,指定一個特殊標誌:

Pattern pattern = Pattern.compile(patternString, Pattern.CASE_INSENSITIVE);

Pattern 類包含多個標誌(int 型別),這些標誌可以控制Pattern 匹配模式的方式。上面程式碼中的標誌使模式匹配是忽略大小寫

Pattern.matcher()

一旦獲得了Pattern物件,接著可以獲得Matcher物件。Matcher 示例用於匹配文字中的模式.示例如下

Matcher matcher = pattern.matcher(text);

Matcher類有一個matches()方法,可以檢查文字是否匹配模式。以下是關於Matcher的一個完整例子

String text    =
        "This is the text to be searched " +
        "for occurrences of the http:// pattern.";
String patternString = ".*http://.*";
Pattern pattern = Pattern.compile(patternString, Pattern.CASE_INSENSITIVE);
Matcher matcher = pattern.matcher(text);
boolean matches = matcher.matches();
System.out.println("matches = " + matches);

Pattern.split()

Pattern 類的 split()方法,可以用正規表示式作為分隔符,把文字分割為String型別的陣列。示例:

String text = "A sep Text sep With sep Many sep Separators";
String patternString = "sep";
Pattern pattern = Pattern.compile(patternString);
String[] split = pattern.split(text);
System.out.println("split.length = " + split.length);
for(String element : split){
    System.out.println("element = " + element);
}

上例中把text 文字分割為一個包含5個字串的陣列。

Pattern.pattern()

Pattern 類的 pattern 返回用於建立Pattern 物件的正規表示式,示例:

String patternString = "sep";
Pattern pattern = Pattern.compile(patternString);
String pattern2 = pattern.pattern();

上面程式碼中 pattern2 值為sep ,與patternString 變數相同。

Matcher (java.util.regex.Matcher)

java.util.regex.Matcher 類用於匹配一段文字中多次出現一個正規表示式,Matcher 也適用於多文字中匹配同一個正規表示式。

Matcher 有很多有用的方法,詳細請參考官方JavaDoc。這裡只介紹核心方法。

以下程式碼演示如何使用Matcher

String text    =
        "This is the text to be searched " +
        "for occurrences of the http:// pattern.";
String patternString = ".*http://.*";
Pattern pattern = Pattern.compile(patternString);
Matcher matcher = pattern.matcher(text);
boolean matches = matcher.matches();

首先建立一個Pattern,然後得到Matcher ,呼叫matches() 方法,返回true 表示模式匹配,返回false表示不匹配。

可以用Matcher 做更多的事。

建立Matcher

通過Pattern 的matcher() 方法建立一個Matcher。

String text    =
        "This is the text to be searched " +
        "for occurrences of the http:// pattern.";

String patternString = ".*http://.*";
Pattern pattern = Pattern.compile(patternString);
Matcher matcher = pattern.matcher(text);

matches()

Matcher 類的 matches() 方法用於在文字中匹配正規表示式

boolean matches = matcher.matches();

如果文字匹配正規表示式,matches() 方法返回true。否則返回false。

matches() 方法不能用於查詢正規表示式多次出現。如果需要,請使用find(), start() 和 end() 方法。

lookingAt()

lookingAt() 與matches() 方法類似,最大的不同是,lookingAt()方法對文字的開頭匹配正規表示式;而

matches() 對整個文字匹配正規表示式。換句話說,如果正規表示式匹配文字開頭而不匹配整個文字,lookingAt() 返回true,而matches() 返回false。 示例:

String text    =
        "This is the text to be searched " +
        "for occurrences of the http:// pattern.";
String patternString = "This is the";
Pattern pattern = Pattern.compile(patternString, Pattern.CASE_INSENSITIVE);
Matcher matcher = pattern.matcher(text);
System.out.println("lookingAt = " + matcher.lookingAt());
System.out.println("matches   = " + matcher.matches());

上面的例子分別對文字開頭和整個文字匹配正規表示式 “this is the”. 匹配文字開頭的方法(lookingAt()) 返回true。

對整個文字匹配正規表示式的方法 (matches()) 返回false,因為 整個文字包含多餘的字元,而 正規表示式要求文字精確匹配”this is the”,前後又不能有額外字元。

find() + start() + end()

find() 方法用於在文字中查詢出現的正規表示式,文字是建立Matcher時,通過 Pattern.matcher(text) 方法傳入的。如果在文字中多次匹配,find() 方法返回第一個,之後每次呼叫 find() 都會返回下一個。

start() 和 end() 返回每次匹配的字串在整個文字中的開始和結束位置。實際上, end() 返回的是字串末尾的後一位,這樣,可以在把 start() 和 end() 的返回值直接用在String.substring() 裡。

String text    =
        "This is the text which is to be searched " +
        "for occurrences of the word 'is'.";
String patternString = "is";
Pattern pattern = Pattern.compile(patternString);
Matcher matcher = pattern.matcher(text);
int count = 0;
while(matcher.find()) {
    count++;
    System.out.println("found: " + count + " : "  + matcher.start() + " - " + matcher.end());
}

這個例子在文字中找到模式 “is” 4次,輸出如下:

found: 1 : 2 - 4
 found: 2 : 5 - 7
 found: 3 : 23 - 25
 found: 4 : 70 - 72

reset()

reset() 方法會重置Matcher 內部的 匹配狀態。當find() 方法開始匹配時,Matcher 內部會記錄截至當前查詢的距離。呼叫 reset() 會重新從文字開頭查詢。

也可以呼叫 reset(CharSequence) 方法. 這個方法重置Matcher,同時把一個新的字串作為引數傳入,用於代替建立 Matcher 的原始字串。

group()

假設想在一個文字中查詢URL連結,並且想把找到的連結提取出來。當然可以通過 start()和 end()方法完成。但是用group()方法更容易些。

分組在正規表示式中用括號表示,例如:

(John)

此正規表示式匹配John, 括號不屬於要匹配的文字。括號定義了一個分組。當正規表示式匹配到文字後,可以訪問分組內的部分。

使用group(int groupNo) 方法訪問一個分組。一個正規表示式可以有多個分組。每個分組由一對括號標記。想要訪問正規表示式中某分組匹配的文字,可以把分組編號傳入 group(int groupNo)方法。

group(0) 表示整個正規表示式,要獲得一個有括號標記的分組,分組編號應該從1開始計算。

String text    =  "John writes about this, and John writes about that," +
                        " and John writes about everything. "  ;
String patternString1 = "(John)";
Pattern pattern = Pattern.compile(patternString1);
Matcher matcher = pattern.matcher(text);
while(matcher.find()) {
    System.out.println("found: " + matcher.group(1));
}

以上程式碼在文字中搜尋單詞John.從每個匹配文字中,提取分組1,就是由括號標記的部分。輸出如下

found: John
 found: John 
found: John

多分組

上面提到,一個正規表示式可以有多個分組,例如:

(John) (.+?)

這個表示式匹配文字”John” 後跟一個空格,然後跟1個或多個字元,最後跟一個空格。你可能看不到最後的空格。

這個表示式包括一些字元有特別意義。字元 點 . 表示任意字元。 字元 + 表示出現一個或多個,和. 在一起表示 任何字元,出現一次或多次。字元? 表示 匹配儘可能短的文字。

完整程式碼如下

String text    =
          "John writes about this, and John Doe writes about that," +
                  " and John Wayne writes about everything."
        ;
String patternString1 = "(John) (.+?) ";
Pattern pattern = Pattern.compile(patternString1);
Matcher matcher = pattern.matcher(text);
while(matcher.find()) {
    System.out.println("found: " + matcher.group(1) +
                       " "       + matcher.group(2));
}

注意程式碼中引用分組的方式。程式碼輸出如下

found: John writes 
found: John Doe 
found: John Wayne

巢狀分組

在正規表示式中分組可以巢狀分組,例如

((John) (.+?))

這是之前的例子,現在放在一個大分組裡.(表示式末尾有一個空格)。

當遇到巢狀分組時, 分組編號是由左括號的順序確定的。上例中,分組1 是那個大分組。分組2 是包括John的分組,分組3 是包括 .+? 的分組。當需要通過groups(int groupNo) 引用分組時,瞭解這些非常重要。

以下程式碼演示如何使用巢狀分組

String text    =
          "John writes about this, and John Doe writes about that," +
                  " and John Wayne writes about everything."
        ;
String patternString1 = "((John) (.+?)) ";
Pattern pattern = Pattern.compile(patternString1);
Matcher matcher = pattern.matcher(text);
while(matcher.find()) {
    System.out.println("found:   ");
}

輸出如下

found: 
 found: 
 found:

replaceAll() + replaceFirst()

replaceAll() 和 replaceFirst() 方法可以用於替換Matcher搜尋字串中的一部分。replaceAll() 方法替換全部匹配的正規表示式,replaceFirst() 只替換第一個匹配的。

在處理之前,Matcher 會先重置。所以這裡的匹配表示式從文字開頭開始計算。

示例如下

String text    =
          "John writes about this, and John Doe writes about that," +
                  " and John Wayne writes about everything."
        ;
String patternString1 = "((John) (.+?)) ";
Pattern pattern = Pattern.compile(patternString1);
Matcher matcher = pattern.matcher(text);

String replaceAll = matcher.replaceAll("Joe Blocks ");
System.out.println("replaceAll   = " + replaceAll);

String replaceFirst = matcher.replaceFirst("Joe Blocks ");
System.out.println("replaceFirst = " + replaceFirst);

輸出如下

replaceAll = Joe Blocks about this, and Joe Blocks writes about that,

and Joe Blocks writes about everything.

replaceFirst = Joe Blocks about this, and John Doe writes about that,

and John Wayne writes about everything.

輸出中的換行和縮排是為了可讀而增加的。

注意第1個字串中所有出現 John 後跟一個單詞 的地方,都被替換為 Joe Blocks 。第2個字串中,只有第一個出現的被替換。

appendReplacement() + appendTail()

appendReplacement() 和 appendTail() 方法用於替換輸入文字中的字串短語,同時把替換後的字串附加到一個 StringBuffer 中。

當find() 方法找到一個匹配項時,可以呼叫 appendReplacement() 方法,這會導致輸入字串被增加到StringBuffer 中,而且匹配文字被替換。 從上一個匹配文字結尾處開始,直到本次匹配文字會被拷貝。

appendReplacement() 會記錄拷貝StringBuffer 中的內容,可以持續呼叫find(),直到沒有匹配項。

直到最後一個匹配專案,輸入文字中剩餘一部分沒有拷貝到 StringBuffer. 這部分文字是從最後一個匹配項結尾,到文字末尾部分。通過呼叫 appendTail() 方法,可以把這部分內容拷貝到 StringBuffer 中.

String text    =
          "John writes about this, and John Doe writes about that," +
                  " and John Wayne writes about everything."
        ;

String patternString1 = "((John) (.+?)) ";
Pattern      pattern      = Pattern.compile(patternString1);
Matcher      matcher      = pattern.matcher(text);
StringBuffer stringBuffer = new StringBuffer();

while(matcher.find()){
    matcher.appendReplacement(stringBuffer, "Joe Blocks ");
    System.out.println(stringBuffer.toString());
}
matcher.appendTail(stringBuffer);
System.out.println(stringBuffer.toString());

注意我們在while迴圈中呼叫appendReplacement() 方法。在迴圈完畢後呼叫appendTail()。 程式碼輸出如下:

Joe Blocks
 Joe Blocks about this, and Joe Blocks
 Joe Blocks about this, and Joe Blocks writes about that, and Joe Blocks
 Joe Blocks about this, and Joe Blocks writes about that, and Joe Blocks
 writes about everything.

Java 正規表示式語法

為了更有效的使用正規表示式,需要了解正規表示式語法。正規表示式語法很複雜,可以寫出非常高階的表示式。只有通過大量的練習才能掌握這些語法規則。

本篇文字,我們將通過例子瞭解正規表示式語法的基礎部分。介紹重點將會放在為了使用正規表示式所需要了解的核心概念,不會涉及過多的細節。詳細解釋,參見 Java DOC 中的 Pattern 類.

基本語法

在介紹高階功能前,我們先快速瀏覽下正規表示式的基本語法。

字元

是正規表示式中最經常使用的的一個表示式,作用是簡單的匹配一個確定的字元。例如:

John

這個簡單的表示式將會在一個輸入文字中匹配John文字。

可以在表示式中使用任意英文字元。也可以使用字元對於的8進位制,16進位制或unicode編碼表示。例如:

101

\x41

\u0041

以上3個表示式 都表示大寫字元A。第一個是8進位制編碼(101),第2個是16進位制編碼(41),第3個是unicode編碼(0041).

字元分類

字元分類是一種結構,可以針對多個字元匹配而不只是一個字元。換句話說,一個字元分類匹配輸入文字中的一個字元,對應字元分類中多個允許字元。例如,你想匹配字元 a,b 或c,表示式如下:

[abc]

用一對方括號[] 表示字元分類。方括號本身並不是要匹配的一部分。

可以用字元分類完成很多事。例如想要匹配單詞John,首字母可以為大寫和小寫J.

[Jj]ohn

字元分類[Jj] 匹配J或j,剩餘的 ohn 會準確匹配字元ohn.

預定義字元分類

正規表示式中有一些預定義的字元分類可以使用。例如, \d 表示任意數字, \s 表示任意空白字元,\w 表示任意單詞字元。

預定義字元分類不需要括在方括號裡,當然也可以組合使用

\d

[\d\s]

第1個匹配任意數字,第2個匹配任意數字或空白符。

完整的預定義字元分類列表,在本文最後列出。

邊界匹配

正規表示式支援匹配邊界,例如單詞邊界,文字的開頭或末尾。例如,\w 匹配一個單詞,^匹配行首,$ 匹配行尾。

^This is a single line$

上面的表示式匹配一行文字,只有文字 This is a single line。注意其中的行首和行尾標誌,表示不能有任何文字在文字的前面後後面,只能是行首和行尾。

完整的匹配邊界列表,在本文最後列出。

量詞匹配

量詞可以匹配一個表示式多次出現。例如下列表示式匹配字母A 出現0次或多次。

A*

量詞 * 表示0次或多次。+ 表示1次或多次。? 表示0次或1次。還有些其他量詞,參見本文後面的列表。

量詞匹配分為 飢餓模式,貪婪模式,獨佔模式。飢餓模式 匹配儘可能少的文字。貪婪模式匹配儘可能多的文字。獨佔模式匹配儘可能多的文字,甚至導致剩餘表示式匹配失敗。

以下演示飢餓模式,貪婪模式,獨佔模式區別。假設以下文字:

John went for a walk, and John fell down, and John hurt his knee.

飢餓模式下 表示式:

John.*?

這個表示式匹配John 後跟0個或多個字元。 . 表示任意字元。* 表示0或多次。? 跟在 * 後面,表示 * 採用飢餓模式。

飢餓模式下,量詞只會匹配儘可能少的字元,即0個字元。上例中的表示式將會匹配單詞John,在輸入文字中出現3次。

如果改為貪婪模式,表示式如下:

John.*

貪婪模式下,量詞會匹配儘可能多的字元。現在表示式會匹配第一個出現的John,以及在貪婪模式下 匹配剩餘的所有字元。這樣,只有一個匹配項。

最後,我們改為獨佔模式:

John.*+hurt

*後跟+ 表示獨佔模式量詞。

這個表示式在輸入文字中沒有匹配項,儘管文字中包括 John 和 hurt. 為什麼會這樣? 因為 .*+ 是獨佔模式。與貪婪模式下,儘可能多的匹配文字,以使整個表示式匹配不同。獨佔模式會盡可能的多的匹配,但不考慮表示式剩餘部分是否能匹配上。

.*+ 將會匹配第一個John之後的所有字元,這會導致表示式中剩餘的 hurt 沒有匹配項。如果改為貪婪模式,會有一個匹配項。表示式如下:

John.*hurt

邏輯操作符

正規表示式支援少量的邏輯運算(與,或,非)。

與操作是預設的,表示式 John ,意味著J 與 o與h與n。

或操作需要顯示指定,用 | 表示。例如表示式 John|hurt 意味著John 或 hurt 。

字元

. 任意英文字母
\\ 反斜槓, 單獨的反斜槓做為轉義字元,與其他特殊字元一起使用。如果想匹配反斜槓本身,需要轉義。兩個反斜槓實際匹配一個反斜槓n字元的8進製表示.n 在0至7之間取值
nn 字元的8進製表示.n 在0至7之間取值
mnn 字元的8進製表示. m 在0至3之間取值, n 在0至7之間取值
\xhh 字元的16進製表示.
\uhhhh 字元的16進製表示 0xhhhh. 對應unicode 編碼字元
\t 縮排符.
\n 換行符 (unicode: ‘\u000A’)
\r 回車符 (unicode: ‘\u000D’)
\f 製表符 (unicode: ‘\u000C’)
\a 警報(鈴聲)字元 (unicode: ‘\u0007′)
\e 轉義符 (unicode: ‘\u001B’)
\cx 控制符 x

字元分類

[abc] 匹配 a, 或 b 或 c
[^abc] 匹配不是a,b,c 的字元,是否定匹配
[a-zA-Z] 匹配a 到 z ,A到Z 直接的字元,是範圍匹配
[a-d[m-p]] 匹配a到d之間字元或 m到p之間字元,是並集匹配
[a-z&&[def]] 匹配 d, e, 或 f. 是交集匹配 (這裡是在範圍 a-z和字元def之間取交集).
[a-z&&[^bc]] 匹配a-z 之間所有字元,排除bc的字元。是減法匹配
[a-z&&[^m-p]] 匹配a-z 之間所有字元,排除m-p之間的字元是減法匹配

內建字元分類

. 匹配任意一個字元,根據建立Pattern是傳入的標誌,可能匹配行結尾符
\d 匹配任意數字 [0-9]
\D 匹配任意非數字 [^0-9]
\s 匹配任意空白符 (空格, 縮排, 換行,回車)
\S 匹配任意非空白符
\w 匹配任意單詞
\W 匹配任意非單詞

邊界匹配

^ 匹配行首
$ 匹配行尾
\b 匹配單詞邊界
\B 匹配非單詞邊界
\A 匹配文字開頭
\G 匹配前一匹配項結尾
\Z Matches the end of the input text except the final terminator if any
\z 匹配文字結尾

量詞

貪婪模式 飢餓模式 獨佔模式
X? X?? X?+ 匹配0或1次
X* X*? X*+ 匹配0或多次
X+ X+? X++ 匹配1或多次
X{n} X{n}? X{n}+ 匹配n次
X{n,} X{n,}? X{n,}+ 匹配最少n次
X{n, m} X{n, m}? X{n, m}+ 匹配最少n次,最多m次

相關文章