在前面的幾個章節中,我們簡單的學習了一些基本的正規表達式的一些元素,今天,我們來讨論一下Java 正規表達式重要的一個概念–Quantifiers(量詞).
啥為量詞?從字面意義上可能和數量有關,其實Java 正則裡面的量詞被用來需要指定某種模式需要重複出現一定次數的情況,比如:我們想比對100個連續的
a
, 當然了,你可以寫100個
a
的正規表達式來達到同樣的功能,但是,這種也太辛苦了,我們程式員是很懶的。是以學習Quantifier相關知識,可以少搬一點磚<_
1.Quantifier的分類
在Java的Pattern類的java doc中其描述了三種類型的,下面列舉三種的内容:
Greedy | Reluctant | Possessive | Meaning | 翻譯 |
---|---|---|---|---|
X? | X?? | X?+ | X, once or not at all | X字元出現一次或者沒有比對 |
X* | X*? | X*+ | X, zero or more times | X字元出現0次或者多次 |
X+ | X+? | X++ | X, one or more times | X字元至少重複出現1次 |
X{n} | X{n}? | X{n}+ | X, exactly n times | X字元重複n次 |
X{n,} | X{n,}? | X{n,}+ | X, at least n times | X字元至少重複n次 |
X{n,m} | X{n,m}? | X{n,m}+ | X, at least n but not more than m times | X字元至少出現n次,但不會超過m次 |
上面幾種類型對應的中文描述是: Greedy–貪婪型(最大比對) Reluctant–勉強型(最小比對), Possessive—占有型(全部比對),在後續内容我将繼續使用英文。
咋眼一看,
X?
,
X??
和
X?+
做着同樣的事情,它們都是用來比對
字元X出現一次或者沒有出現
, 其實這個問題開始學習确實煩的要死,後面的部分說明它們之間的細微差别。
這裡讓大家了解一下
*
,
?
和
+
這幾個Metacharater的意思,下面幾個實驗中,帶比對的空字元串,即”“.

通過上面的幾個例子,我們簡單列舉它們具體的意義:
1.
*
:字元出現的次數>=0;
2.
?
: 字元出現的次數要麼是1,要麼是0次;
3.
+
:字元出現的次數>=1;
2. Zero-Length Matches(零長度比對)
這個概念有點奇怪,長度為0的比對是什麼鬼?請大家之前說的比對start index和 end index.可以通過下面的圖了解一下:
上面是某次比對的結果,比對的子串是原字元串索引1到3為止,不包括3,是以這裡面的end index指的是下一次繼續尋找比對子串的起始索引。
那這個和我們現在要說的零長度比對有什麼關系嗎? 認真一點的可能會看到上面的頭兩個示例中,出現了start index = 0 和end index也為0的情況,這個怎麼解釋呢?
首先我們要了解一下
a?
和
a*
這兩個表達式都允許字元
a
出現0次的情況,考慮到我們輸入的是空字元串,字元串的長度為0, 這時候他們去比對0長度的時候,沒有出現字元
a
,說明也滿足上面兩個正規表達式的條件,這種類型的比對我們稱之為“零長度比對”,它們特征也很明顯,就是start index和 end index相等。
這裡大概可能有疑問了,空字元串不是長度為0嗎?你這裡比對index=0,按道理應該說明字元串是長度為一的字元串啊!!!
是時候表演真正的技術了,這裡注意一下end index的意義,上面我有提到它表示下一次比對的開始,這個時候比對過程就是去嘗試比對 end index,這時候我們start index就應該調整到end index, 但是比對的結果讓人很心酸—start index沒有資料,這時候比對引擎回溯,當然這時候回溯後的位置還是start index,此時,構造出的是空白,然而這種空白符合
a?
和
a*
的意,這就是為啥end index = start index, 此時比對過程結束,原因在于start index已經>= 字元串的長度了,說明已經沒有的比對了。
下面我們在通過幾個示例來感受這波騷操作:
下面我将輸入字元串長度變大,大家可以看到各個元字元的差異性了:
下面我們再嘗試一些非純
a
字元的情況:
解釋: abcaad,
第一次比對a, 成功,此時列印Found [a] starting at 0 and ending at 1;
第二次比對b, 失敗,但是比對引擎回溯,此時start index = end index,這樣就構成了空串,正則
a?
比對這種情況,是以列印: Found [a] starting at 1 and ending at 1;
第三次比對c,此時start index為2, 為啥是2不是1呢?原因在于 index = 1的位置我們已經比對過,不要因為比對引擎回溯,而以為start index = 1, 這裡start index應該表示上次已經比對過的字元索引位置的下一位,即沒有比對過的位,現在比對d字元,同比對b一樣,列印:*Found [a] starting at 2 and ending at 2;
第四次比對字元a, 比對成功,則輸出: *Found [a] starting at 3 and ending at 4;
第五次比對字元a, 比對成功,則輸出: *Found [a] starting at 4 and ending at 5;
第六次比對字元d,比對失敗,則輸出: Found [a] starting at 5 and ending at 5;
第七次比對字元d,比對失敗,則輸出: Found [a] starting at 6 and ending at 6;
下面再給出兩個例子:
3. 定長比對
某些情況下,我們希望字元重複出現的次數可控,這是我們可以用大括号表達自己期望的次數,{min, max}.
這個存在三種變形:
Format | Meaning |
---|---|
{num} | 重複num次 |
{min,} | 至少重複num次 |
{min, max} | 重複次數在min<= 重複次數 <= max |
下面給出幾個示例:
上面的學習中,我們僅僅将量詞放在單個字元情況,下面我們來讨論量詞用在Capturing Groups 和 Character classes的情況.
4. 量詞和Capturing Groups和Character Classes的使用
量詞用在Capturing Groups和Character Classes時,我們将它們當做一個整體來看待,下面通過例子來了解:
上面的例子在比對
(dog){3}
時,将括号裡面的dog看做一個整體來,即dog字元串得重複出現三次,也就是
dogdogdog
,
然而
dog{3}
我們這裡的{3}修飾的
g
字元,它比對的字元是
doggg
這種情況.
下面我們來了解一下Character Classes的情況:
對于
[abc]{3}
表示重複出現的三個字元,可以是字元
a
,
b
或
c
中任意一個,是以比對到可能的子串為: Character Classes Length * repeat Num
5. Greedy、Reluctant和Possessive的差別
Greedy: 從字面意義來看它是貪婪,想一口氣吃一個大胖子,它在比對的時候,總是先整個字元串比對,比對不了,它在回退,先看個例子:
上面的例子中
.*foo
先将整個字元串進行比對,結果發現模式比對,就傳回整個字元串作為結果.
reluctant: 表示最小比對,它總是小口小口的吃,可以通過例子比較一下差別:
Possessive:表示最大比對,而且它就比對一下,比對不成功,比對引擎不會回溯的,可以通過如下例子了解:
這個為啥不比對呢?原因在于對于
.*+foo
而言,
.*+
比對完了整個字元,當它繼續比對foo的時候發現找不到了。因為此時沒有可供比對的字元的,故傳回”Not Found”。
大概可能就要問了這個Possessive有什麼使用場景呢?這個通常用在嚴格模式上面的比對,比如郵箱比對之類的場景。
完!
上一節: 【Java正規表達式系列】5. 預定義Character classes
下一節: 【Java正規表達式系列】7 Capturing Groups(比對組)