正則指引之量詞

通常形式javascript

假如咱們要驗證郵政編碼:201203,100858,因此用正則表達式來表示就是 \d\d\d\d\d\d,只有同時知足「長度是6個字符」和「每一個字符都是數字」兩個條件,匹配才成功。雖然這不難理解,但 \d 重複6次,讀寫都不方便。爲此,正則表達式提供了量詞(quantifier)。那麼上面的例子就能夠簡寫爲 \d{6},它使用阿拉伯數字,更簡潔也更直觀。html

//使用量詞減化字符組
String text = "510850";
Pattern p = Pattern.compile("\\d{6}");
Matcher m = p.matcher(text);
System.out.println(m.matches());    //true

量詞還能夠表示不肯定的長度,其通用形式是{m,n},其中m和n是兩個數字(有些人習慣在代碼中的逗號以後添加空格,這樣更好看,可是量詞中的逗號以後毫不能有空格),它限定以前的元素可以出現的次數,m是下限,n是上限(均爲閉區間)。好比 \d{4,6},就表示這個數字字符串的長度最短是4個字符,最長是6個字符。若是不肯定長度的上限,也能夠省略,只指定下限,寫成 \d{n,},好比 \d{4,}表示「數字字符串的長度必須在4個字符以上」。java

量詞                                                       
說明                                                                                        
{n}
以前的元素必須出現n次
{m,n}
以前的元素最少出現m次,最多出現n次
{m,}
以前的元素最少出現m次,出現次數無上限
{0,n}
以前的元素能夠不出現,也能夠出現,最多出現n次

經常使用量詞正則表達式

{m,n}是通用形式的量詞,正則表達式還有三個經常使用量詞,分別是 +? 。它們的形態雖然不一樣於{m,n},功能倒是相同的(也能夠把它們理解爲「量詞簡記法」),具體說明以下:測試

經常使用量詞            
{m,n}等價形式                 
說明                                                      

{0,}
可能出現,也可能不出現,出現次數沒有上限

{1,}
至少出現1次,出現次數沒有上限

{0,1}
至多出現一次,也可能不出現

在實際應用中,在不少狀況下只須要表示這三種意思,因此經常使用量詞的使用頻率要高於{m,n}。編碼

點號spa

前一節講到了各類字符組,與它相關的還有一個特殊的元字符:點號(.)。通常文檔都說,點號能夠匹配「任意字符」,點號確實能夠匹配「任意字符」,常見的數字,字母,各類符號均可以匹配,但有一個字符不能由點號匹配,就是換行符 \n。這個字符平時看不見,卻存在,並且在處理時並不能忽略。翻譯

若是非要匹配「任意字符」,有兩種辦法:能夠指定使用單行匹配模式,在這種模式下,點號能夠匹配換行符;或者使用上一節介紹「自制」通配字符組 [\s\S] (也可使用 [\d\D][\w\W]),正好涵蓋了全部字符。示例:code

//換行符的匹配
String text = "\n";
Pattern p = Pattern.compile("[\\d\\D]");
Matcher m = p.matcher(text);
System.out.println(m.matches());    //true
Pattern p1 = Pattern.compile(".");
Matcher m1 = p1.matcher(text);
System.out.println(m1.matches());      //false

濫用點號的問題htm

由於點號能匹配幾乎全部的字符,因此實際應用中許多人圖省事,隨意使用 .* 或 .+ ,結果卻事與願違,下面以雙引號字符串爲例來講明。咱們通常使用表達式 」[^"]*" 匹配雙引號字符串,而「圖省事」 的作法是 「.*」  。一般這麼用是沒有問題的,但也可能有意外

用「.*」匹配雙引號字符串,不但能夠匹配正常的雙引號字符串「quoted string」,還能夠匹配格式錯誤的字符串 "quoted string" and another" 。這是爲何呢?緣由涉及正則表達式的匹配原理

在正則表達式 「.*」 中,點號能夠匹配任何字符,*表示能夠匹配的字符串長度沒有限制,因此 .* 在匹配過程結束之前,每遇到一個字符(除去沒法匹配的 \n),.*均可以匹配,可是遇到第一個 " 時,究竟是匹配這個字符仍是忽略它,仍是將其交給以後的 " 來匹配呢?

答案是,具體選擇取決於所使用的量詞。在正則表達式中的量詞分爲幾類,以前介紹的量詞均可以歸到一類,叫作匹配優先量詞(貪婪量詞)。匹配優先量詞,顧名思義,就是在拿不許是否要匹配的時候,優先嚐試匹配,而且記下這個狀態,以備未來「反悔」。

來看錶達式「.*」對字符串「quoted string」的匹配過程。一開始, 「 匹配 「 ,而後輪到字符q,.*能夠匹配它,也能夠不匹配,由於使用了匹配優先量詞,因此.*先匹配q,而且記錄下這個狀態「q也多是.*不該該匹配的」;接下來是字符u,.*能夠匹配它,也能夠不匹配,由於使用了匹配優先量詞,因此.*先匹配u,而且記錄下這個狀態『u也多是.*不該該匹配的』; .......如今輪到了字符g,.*能夠匹配它,也能夠不匹配,由於使用了匹配優先量詞,因此 .*先匹配g,而且記錄下這個狀態『g也多是.*不該該匹配的』。最後是末尾的",.*能夠匹配它,也能夠不匹配,由於使用了匹配優先量詞,因此.*先匹配 「,而且記錄下這個狀態『'也多是.*不該該匹配的』。這時候,字符串以後已經沒有字符了,但正則表達式中還有 」 沒有匹配,因此只能查詢以前保存備用的狀態,看看能不能退回幾步,照顧 " 的匹配。查詢到最近保存的狀態是:「"也多是.*不該該匹配的」。因而讓.* 反悔對 " 的匹配,把 " 交給 「 ,測試發現正好能匹配,因此整個匹配宣告成功。這個反悔的過程,專業術語叫作回溯(backtracking)。

忽略優先量詞

好比,用一個正則表達式匹配下面這段HTML源代碼:

<script type="text/javascript">
    alert("some punctuation <>/");
</script>

開頭和結尾的tag都容易匹配,中間的代碼要比較麻煩,由於點號. 不能匹配換行符,因此必須使用[\s\S],[\d\D]或者[\w\W]。

<script type="text/javascript">[\s\S]*</script>

這個表達式確實能夠匹配上面的Javascript代碼。可是若是遇到更復雜的狀況就會出錯,好比針對下面這段HTML代碼:

<script type="text/javascript">
alert("1");
</script>
<br />
<script type="text/javascript">
alert("2");
</script>

若是用上面的表達式來匹配這段HTML代碼,會一次性匹配兩段Javascript代碼,甚至包含之間的非Javascript代碼按照匹配原理,[\s\S]*先匹配全部的文本,回溯時交還最後的</script>,整個表達式的匹配就成功了,邏輯就是如此,無可改進。並且,這個問題也不能模仿以前雙引號字符串匹配,用[^"]*區配<script...>和</script>之間的代碼,由於排除型字符組只能排除單個字符,[^</script>]不能表示「不是</script>的字符串」。

換個角度來看,經過改變[\s\S]*的匹配策略解決問題在不肯定是否要匹配的場合,先嚐試不匹配的選擇,測試正則表達式中後面的元素,若是失敗,再退回來嘗試 [\s\S]* 匹配,如此就沒有問題了。循着這個思路,正則表達式中還提供了忽略優先量詞(lazy quantifier 或 reluctant quantifier,也就有翻譯爲懶惰量詞),若是不肯定是否要匹配,忽略優先量詞會選擇「不匹配」的狀態,再嘗試表達式中以後的元素,若是嘗試失敗,再回溯,選擇以前保存的「匹配」的狀態。

對[\s\S]*來講,把*改成*?就是使用了忽略優先量詞,*?限定的元素出現次數範圍與*徹底同樣,都表示「可能出現,也可能不出現,出現次數沒有上限」。區別在於,在實際匹配過程當中,遇到[\s\S]能匹配的字符,先嚐試「忽略」,若是後面的元素(具體到這個表達式中,是</script>)不能匹配,再嘗試匹配,這樣就保證告終果的正確性,示例以下:

//忽略優先量詞
String text = "<script type=\"text/javascript\">" +
        "alert(\"1\")" +
        "</script>" + 
        "<br />" + 
        "<script type=\"text/javascript\">"+
        "alert(\"2\")"+
        "</script>";
Pattern p = Pattern.compile("<script type=\"text/javascript\">[\\s\\S]*?</script>");
Matcher m = p.matcher(text);
while(m.find()){
    System.out.println(m.group());
}

表:匹配優先量詞與忽略優先量詞

匹配優先量詞        
忽略優先量詞             
限定次數                                                 
*
*?
可能不出現,也可能出現,出現次數沒有上限
+
+?
至少出現1次,出現次數沒有上限
?
??
至多出現1次,也可能不出現
{m,n}
{m,n}?
出現次數最少爲m次,最多爲n次
{m,}
{m,}?
出現次數最少爲m次,沒有上限
{0,n}
{0,n}?
可能不出現,也可能出現,最多出現n次

從上表能夠看到,匹配優先量詞與忽略優先量詞逐一對應,只是在對應的匹配優先量詞以後添加?,二者限定的元素能出現的次數也同樣,遇到不能匹配的狀況一樣須要回溯; 惟一的區別在於,忽略優先量詞會優先選擇「忽略」,而匹配優先量詞會優先選擇「匹配」

轉義

前面講解了匹配優先量詞和忽略優先量詞,如今介紹量詞的轉義。在正則表達式中,*,+,?等做爲量詞的字符具備特殊意義,但有些狀況下只但願表示這些字符自己,此時就必須使用轉義,也就是在它們以前添加反斜線 \

對經常使用量詞所使用的字符+,*,? 來講,若是但願表示這三個字符自己,直接添加反斜線,變爲 \+,\*,\? 便可。可是在通常形式的量詞{m,n}中,雖然具備特殊含義的字符不止一個,轉義時卻只須要給第一個 { 添加反斜線便可,也就是說,若是但願匹配字符串{m,n},正則表達式必須寫成 \{m,n} 。

另外值得一提的是忽略優先量詞的轉義,雖然忽略優先量詞也包含不僅一個字符,可是在轉義時卻不像通常形式的量詞那樣,只轉義第一個字符便可,而須要將兩個量詞所有轉義。舉例來講,若是要匹配字符串 *?,正則表達式就必須寫做 \*\? ,而不是 \*? ,由於後者的意思是「*這個字符可能出現,也可能不出現」。各類量詞的轉義以下:

量詞                                    
轉義形式                                                                    
{n}
\{n}
{m,n}
\{m,n}
{m,}
\{m,}
{0,n}
\{0,n}
*
\*
+
\+
?
\?
*?
\*\?
+?
\+\?
??
\?\?

以前還介紹了點號 . ,因此還必須講解點號的轉義:點號(.)是一個元字符,它能夠匹配除換行符以外的任何字符,因此若是隻想匹配點號自己,必須將它轉義爲 \. 。由於未轉義的點號能夠匹配任何字符,其中也能夠包含點號,因此常常有人忽略了對點號的轉義。

相關文章
相關標籤/搜索