天天看點

Java 正規表達式正規表達式Java6 中關于正規表達式的APIJava 正規表達式文法

原文位址   作者:jakob jenkov 譯者:嚴亮

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

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

下面是一個簡單的java正規表達式的例子,用于在文本中搜尋 http://

<code>1</code>

<code>string text    =</code>

<code>2</code>

<code>        </code><code>"this is the text to be searched "</code> <code>+</code>

<code>3</code>

<code>        </code><code>"for occurrences of the http:// pattern."</code><code>;</code>

<code>4</code>

<code>string pattern =</code><code>".*http://.*"</code><code>;</code>

<code>5</code>

<code>boolean</code> <code>matches = pattern.matches(pattern, text);</code>

<code>6</code>

<code>system.out.println(</code><code>"matches = "</code> <code>+ matches);</code>

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

本教程介紹了java6 中關于正規表達式的api。

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

檢查一個正規表達式的模式是否比對一段文本的最直接方法是調用靜态方法pattern.matches(),示例如下:

<code>        </code><code>"for occurrences of the pattern."</code><code>;</code>

<code>string pattern =</code><code>".*is.*"</code><code>;</code>

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

pattern.matches() 方法适用于檢查 一個模式在一個文本中出現一次的情況,或适用于pattern類的預設設定。

如果需要比對多次出現,甚至輸出不同的比對文本,或者隻是需要非預設設定。需要通過pattern.compile() 方法得到一個pattern 執行個體。

如果需要比對一個正規表達式在文本中多次出現,需要通過pattern.compile() 方法建立一個pattern對象。示例如下

<code>string patternstring =</code><code>".*http://.*"</code><code>;</code>

<code>pattern pattern = pattern.compile(patternstring);</code>

可以在compile 方法中,指定一個特殊标志:

<code>pattern pattern = pattern.compile(patternstring, pattern.case_insensitive);</code>

pattern 類包含多個标志(int 類型),這些标志可以控制pattern 比對模式的方式。上面代碼中的标志使模式比對是忽略大小寫

一旦獲得了pattern對象,接着可以獲得matcher對象。matcher 示例用于比對文本中的模式.示例如下

<code>matcher matcher = pattern.matcher(text);</code>

matcher類有一個matches()方法,可以檢查文本是否比對模式。以下是關于matcher的一個完整例子

<code>7</code>

<code>boolean</code> <code>matches = matcher.matches();</code>

<code>8</code>

pattern 類的 split()方法,可以用正規表達式作為分隔符,把文本分割為string類型的數組。示例:

<code>string text =</code><code>"a sep text sep with sep many sep separators"</code><code>;</code>

<code>string patternstring =</code><code>"sep"</code><code>;</code>

<code>string[] split = pattern.split(text);</code>

<code>system.out.println(</code><code>"split.length = "</code> <code>+ split.length);</code>

<code>for</code><code>(string element : split){</code>

<code>    </code><code>system.out.println(</code><code>"element = "</code> <code>+ element);</code>

<code>}</code>

上例中把text 文本分割為一個包含5個字元串的數組。

pattern 類的 pattern 傳回用于建立pattern 對象的正規表達式,示例:

<code>string pattern2 = pattern.pattern();</code>

上面代碼中 pattern2 值為sep ,與patternstring 變量相同。

java.util.regex.matcher 類用于比對一段文本中多次出現一個正規表達式,matcher 也适用于多文本中比對同一個正規表達式。

matcher 有很多有用的方法,詳細請參考官方javadoc。這裡隻介紹核心方法。

以下代碼示範如何使用matcher

首先建立一個pattern,然後得到matcher ,調用matches() 方法,傳回true 表示模式比對,傳回false表示不比對。

可以用matcher 做更多的事。

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

matcher 類的 matches() 方法用于在文本中比對正規表達式

如果文本比對正規表達式,matches() 方法傳回true。否則傳回false。

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

lookingat() 與matches() 方法類似,最大的不同是,lookingat()方法對文本的開頭比對正規表達式;而

matches() 對整個文本比對正規表達式。換句話說,如果正規表達式比對文本開頭而不比對整個文本,lookingat() 傳回true,而matches() 傳回false。 示例:

<code>string patternstring =</code><code>"this is the"</code><code>;</code>

<code>system.out.println(</code><code>"lookingat = "</code> <code>+ matcher.lookingat());</code>

<code>system.out.println(</code><code>"matches   = "</code> <code>+ matcher.matches());</code>

上面的例子分别對文本開頭和整個文本比對正規表達式 “this is the”. 比對文本開頭的方法(lookingat()) 傳回true。

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

find() 方法用于在文本中查找出現的正規表達式,文本是建立matcher時,通過 pattern.matcher(text) 方法傳入的。如果在文本中多次比對,find() 方法傳回第一個,之後每次調用 find() 都會傳回下一個。

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

<code>01</code>

<code>02</code>

<code>        </code><code>"this is the text which is to be searched "</code> <code>+</code>

<code>03</code>

<code>        </code><code>"for occurrences of the word 'is'."</code><code>;</code>

<code>04</code>

<code>string patternstring =</code><code>"is"</code><code>;</code>

<code>05</code>

<code>06</code>

<code>07</code>

<code>int</code> <code>count =</code><code>0</code><code>;</code>

<code>08</code>

<code>while</code><code>(matcher.find()) {</code>

<code>09</code>

<code>    </code><code>count++;</code>

<code>10</code>

<code>    </code><code>system.out.println(</code><code>"found: "</code> <code>+ count +</code><code>" : "</code>  <code>+ matcher.start() +</code><code>" - "</code> <code>+ matcher.end());</code>

<code>11</code>

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

reset() 方法會重置matcher 内部的 比對狀态。當find() 方法開始比對時,matcher 内部會記錄截至目前查找的距離。調用 reset() 會重新從文本開頭查找。

也可以調用 reset(charsequence) 方法. 這個方法重置matcher,同時把一個新的字元串作為參數傳入,用于代替建立 matcher 的原始字元串。

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

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

(john)

此正規表達式比對john, 括号不屬于要比對的文本。括号定義了一個分組。當正規表達式比對到文本後,可以通路分組内的部分。

使用group(int groupno) 方法通路一個分組。一個正規表達式可以有多個分組。每個分組由一對括号标記。想要通路正規表達式中某分組比對的文本,可以把分組編号傳入 group(int groupno)方法。

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

<code>string text    = </code><code>"john writes about this, and john writes about that,"</code> <code>+</code>

<code>                        </code><code>" and john writes about everything. "</code>  <code>;</code>

<code>string patternstring1 =</code><code>"(john)"</code><code>;</code>

<code>pattern pattern = pattern.compile(patternstring1);</code>

<code>    </code><code>system.out.println(</code><code>"found: "</code> <code>+ matcher.group(</code><code>1</code><code>));</code>

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

<code>(john) (.+?)</code>

這個表達式比對文本”john” 後跟一個空格,然後跟1個或多個字元,最後跟一個空格。你可能看不到最後的空格。

這個表達式包括一些字元有特别意義。字元 點 . 表示任意字元。 字元 + 表示出現一個或多個,和. 在一起表示 任何字元,出現一次或多次。字元? 表示 比對盡可能短的文本。

完整代碼如下

<code>          </code><code>"john writes about this, and john doe writes about that,"</code> <code>+</code>

<code>                  </code><code>" and john wayne writes about everything."</code>

<code>        </code><code>;</code>

<code>string patternstring1 =</code><code>"(john) (.+?) "</code><code>;</code>

<code>    </code><code>system.out.println(</code><code>"found: "</code> <code>+ matcher.group(</code><code>1</code><code>) +</code>

<code>                       </code><code>" "</code>       <code>+ matcher.group(</code><code>2</code><code>));</code>

在正規表達式中分組可以嵌套分組,例如

<code>((john) (.+?))</code>

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

當遇到嵌套分組時, 分組編号是由左括号的順序确定的。上例中,分組1 是那個大分組。分組2 是包括john的分組,分組3 是包括 .+? 的分組。當需要通過groups(int groupno) 引用分組時,了解這些非常重要。

以下代碼示範如何使用嵌套分組

<code>string patternstring1 =</code><code>"((john) (.+?)) "</code><code>;</code>

<code>    </code><code>system.out.println(</code><code>"found:   "</code><code>);</code>

replaceall() 和 replacefirst() 方法可以用于替換matcher搜尋字元串中的一部分。replaceall() 方法替換全部比對的正規表達式,replacefirst() 隻替換第一個比對的。

在處理之前,matcher 會先重置。是以這裡的比對表達式從文本開頭開始計算。

示例如下

<code>string replaceall = matcher.replaceall(</code><code>"joe blocks "</code><code>);</code>

<code>system.out.println(</code><code>"replaceall   = "</code> <code>+ replaceall);</code>

<code>12</code>

<code>string replacefirst = matcher.replacefirst(</code><code>"joe blocks "</code><code>);</code>

<code>13</code>

<code>system.out.println(</code><code>"replacefirst = "</code> <code>+ replacefirst);</code>

輸出如下

<code>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.</code>

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

注意第1個字元串中所有出現 john 後跟一個單詞 的地方,都被替換為 joe blocks 。第2個字元串中,隻有第一個出現的被替換。

appendreplacement() 和 appendtail() 方法用于替換輸入文本中的字元串短語,同時把替換後的字元串附加到一個 stringbuffer 中。

當find() 方法找到一個比對項時,可以調用 appendreplacement() 方法,這會導緻輸入字元串被增加到stringbuffer 中,而且比對文本被替換。 從上一個比對文本結尾處開始,直到本次比對文本會被拷貝。

appendreplacement() 會記錄拷貝stringbuffer 中的内容,可以持續調用find(),直到沒有比對項。

直到最後一個比對項目,輸入文本中剩餘一部分沒有拷貝到 stringbuffer. 這部分文本是從最後一個比對項結尾,到文本末尾部分。通過調用 appendtail() 方法,可以把這部分内容拷貝到 stringbuffer 中.

檢視源代碼

列印幫助

<code>pattern      pattern      = pattern.compile(patternstring1);</code>

<code>matcher      matcher      = pattern.matcher(text);</code>

<code>stringbuffer stringbuffer =</code><code>new</code> <code>stringbuffer();</code>

<code>while</code><code>(matcher.find()){</code>

<code>    </code><code>matcher.appendreplacement(stringbuffer,</code><code>"joe blocks "</code><code>);</code>

<code>    </code><code>system.out.println(stringbuffer.tostring());</code>

<code>14</code>

<code>15</code>

<code>matcher.appendtail(stringbuffer);</code>

<code>16</code>

<code>system.out.println(stringbuffer.tostring());</code>

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

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

在介紹進階功能前,我們先快速浏覽下正規表達式的基本文法。

是正規表達式中最經常使用的的一個表達式,作用是簡單的比對一個确定的字元。例如:

<code>john</code>

這個簡單的表達式将會在一個輸入文本中比對john文本。

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

<code>101 \x41 \u0041</code>

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

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

<code>[abc]</code>

用一對方括号[] 表示字元分類。方括号本身并不是要比對的一部分。

可以用字元分類完成很多事。例如想要比對單詞john,首字母可以為大寫和小寫j.

<code>[jj]ohn</code>

字元分類[jj] 比對j或j,剩餘的 ohn 會準确比對字元ohn.

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

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

<code>\d [\d\s]</code>

第1個比對任意數字,第2個比對任意數字或空白符。

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

正規表達式支援比對邊界,例如單詞邊界,文本的開頭或末尾。例如,\w 比對一個單詞,^比對行首,$ 比對行尾。

<code>^this is a single line$</code>

上面的表達式比對一行文本,隻有文本 this is a single line。注意其中的行首和行尾标志,表示不能有任何文本在文本的前面後後面,隻能是行首和行尾。

完整的比對邊界清單,在本文最後列出。

量詞可以比對一個表達式多次出現。例如下清單達式比對字母a 出現0次或多次。

<code>a*</code>

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

量詞比對分為 饑餓模式,貪婪模式,獨占模式。饑餓模式 比對盡可能少的文本。貪婪模式比對盡可能多的文本。獨占模式比對盡可能多的文本,甚至導緻剩餘表達式比對失敗。

以下示範饑餓模式,貪婪模式,獨占模式差別。假設以下文本:

<code>john went for a walk, and john fell down, and john hurt his knee.</code>

饑餓模式下 表達式:

<code>john.*?</code>

這個表達式比對john 後跟0個或多個字元。 . 表示任意字元。* 表示0或多次。? 跟在 * 後面,表示 * 采用饑餓模式。

饑餓模式下,量詞隻會比對盡可能少的字元,即0個字元。上例中的表達式将會比對單詞john,在輸入文本中出現3次。

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

<code>john.*</code>

貪婪模式下,量詞會比對盡可能多的字元。現在表達式會比對第一個出現的john,以及在貪婪模式下 比對剩餘的所有字元。這樣,隻有一個比對項。

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

<code>john.*+hurt</code>

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

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

.*+ 将會比對第一個john之後的所有字元,這會導緻表達式中剩餘的 hurt 沒有比對項。如果改為貪婪模式,會有一個比對項。表達式如下:

<code>john.*hurt</code>

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

與操作是預設的,表達式 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&amp;&amp;[def]]

比對 d, e, 或 f. 是交集比對 (這裡是在範圍 a-z和字元def之間取交集).

[a-z&amp;&amp;[^bc]]

比對a-z 之間所有字元,排除bc的字元。是減法比對

[a-z&amp;&amp;[^m-p]]

比對a-z 之間所有字元,排除m-p之間的字元是減法比對

比對任意一個字元,根據建立pattern是傳入的标志,可能比對行結尾符

\d

比對任意數字 [0-9]

比對任意非數字 [^0-9]

\s

比對任意空白符 (空格, 縮進, 換行,回車)

比對任意非空白符

\w

比對任意單詞

比對任意非單詞

^

比對行首

$

比對行尾

\b

比對單詞邊界

比對非單詞邊界

比對文本開頭

\g

比對前一比對項結尾

\z

matches the end of the input text except the final terminator if any

比對文本結尾

貪婪模式

饑餓模式

獨占模式

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次