貪婪模式
*和+這兩個(gè)限定符都可以標(biāo)識(shí)匹配多個(gè)元素,在默認(rèn)情況下它們會(huì)盡可能多的匹配文字,這就是所謂的“貪婪模式”
示例代碼
### 測(cè)試代碼
@Test
public void test() throws IOException {
String str = "<xml>helloworld<xml>";
String regex = "<.*>";
Pattern pattern = Pattern.compile(regex);
Matcher matcher = pattern.matcher(str);
while (matcher.find()) {
System.out.println(matcher.group());
}
}
###輸出結(jié)果
<xml>helloworld<xml>
Process finished with exit code 0
注意觀察上述代碼,按照匹配規(guī)則,"<.>"表示匹配以‘<’開頭,‘>’結(jié)尾,中間有任意個(gè)字符的字符串。那么很明顯第一個(gè)<xml>和最后一個(gè)</xml>也是滿足匹配規(guī)則的,但是結(jié)果只輸出了整個(gè)字符串。這就是限定符的貪婪模式,會(huì)盡可能多的匹配字符,也就是說它匹配了“xml>helloworld<xml”這一整串內(nèi)容。
怎么消除這種現(xiàn)象讓它能匹配到第一個(gè)滿足條件的“<xml>”和最后一個(gè)“</xml>”呢?
示例代碼
@Test
public void test() throws IOException {
String str = "<xml>helloworld<xml>";
String regex = "<.*?>";
Pattern pattern = Pattern.compile(regex);
Matcher matcher = pattern.matcher(str);
while (matcher.find()) {
System.out.println(matcher.group());
}
}
### 輸出結(jié)果
<xml>
<xml>
Process finished with exit code 0
將匹配的字符串從"<.>" 變更為 "<.?>",也就是在限定符后面加“?”即可消除貪婪模式
分組即“()”在正則表達(dá)式中的作用
使限定符可以作用于成對(duì)的匹配規(guī)則
@Test
public void test() throws IOException {
String str = "abcabcabc";
String regex = "(abc){3}";
Pattern pattern = Pattern.compile(regex);
Matcher matcher = pattern.matcher(str);
while (matcher.find()) {
System.out.println(matcher.group());
}
}
### 輸出結(jié)果
abcabcabc
Process finished with exit code 0
可以看到,通過"()"將“abc”字符包裹之后,限定符"{3}"的修飾內(nèi)容變成了“abc”整體出現(xiàn)三次,于是成功匹配了我們的目標(biāo)字符串“abcabcabc”.
檢索字符串
所謂檢索字符串,可以理解為提取目標(biāo)字符串,例如:對(duì)于字符串“<xml>helloworld<xml>”,通過檢索可以提取出<xml></xml>標(biāo)簽的內(nèi)容“helloworld”。
在理解檢索之前,順帶提一下java中matcher類,我們常用的方法有2個(gè)
matcher.matches();
matcher.find();
他們之間的區(qū)別在于:
matches()方法意為匹配,即目標(biāo)字符串“整體”與我們的“正則表達(dá)式”進(jìn)行匹配,返回結(jié)果是true或者false,這種情況通常用在例如:郵箱,電話號(hào)碼等場(chǎng)景,用來檢查輸入的正確與否。
find()方法意為查找,也就是說它并不強(qiáng)調(diào)整體匹配是否正確,只是按照我們的“正則表達(dá)式”規(guī)則到目標(biāo)字符串中查找是否有相匹配的字符串,其實(shí)就是部分匹配的意思。
示例代碼
@Test
public void test() throws IOException {
String str = "helloworld";
String regex = "hello";
Pattern pattern = Pattern.compile(regex);
Matcher matcher = pattern.matcher(str);
System.out.print(matcher.matches());
// while (matcher.find()) {
// System.out.println(matcher.group());
// }
}
### 輸出結(jié)果 false 這是因?yàn)?hello"字符串與"helloworld"是不相匹配的
上述代碼更改為
@Test
public void test() throws IOException {
String str = "helloworld";
String regex = "hello";
Pattern pattern = Pattern.compile(regex);
Matcher matcher = pattern.matcher(str);
// System.out.print(matcher.matches());
while (matcher.find()) {
System.out.println(matcher.group());
}
}
### 輸出結(jié)果 hello
這是因?yàn)樵谀繕?biāo)字符串"helloworld"中找到了與我們的匹配規(guī)則相符的字符串"hello"
find()方法的匹配是從左到右的,也就是說如果字符串中有多個(gè)滿足表達(dá)式的子字符串,通過多次find可以全部找出。示例代碼:
@Test
public void test2() throws IOException {
String str = "helloworldhello";
String regex = "hello";
Matcher matcher = Pattern.compile(regex).matcher(str);
//必須先調(diào)用matcher.find(),這樣才會(huì)將匹配的結(jié)果放到group中
int n = 0;
while (matcher.find()) {
System.out.println(n++);
int count = matcher.groupCount();
for (int i = 0; i <= count; i++) {
System.out.println("matcher.group(" + i + "): " + matcher.group(i));
}
}
}
###輸出結(jié)果
0
matcher.group(0): hello
1
matcher.group(0): hello
以上代碼第一次find找到了第一個(gè)hello,第二次find找到了world后面的hello
看完上述代碼再來講下如何使用"()"的檢索功能
示例代碼
@Test
public void test() throws IOException {
String str = "<xml>helloworld</xml>";
String regex = "<xml>([a-z]*)</xml>";
Matcher matcher = Pattern.compile(regex).matcher(str);
//必須先調(diào)用matcher.find(),這樣才會(huì)將匹配的結(jié)果放到group中
if (matcher.find()) {
int count = matcher.groupCount();
for (int i = 0; i <= count; i++) {
System.out.println("matcher.group(" + i + "): " + matcher.group(i));
}
}
}
###輸出結(jié)果
matcher.group(0): <xml>helloworld</xml>
matcher.group(1): helloworld
上面代碼,通過給中間匹配加"([a-z])",matcher類會(huì)將這個(gè)分組的匹配結(jié)果放到group(1),稱為捕獲組1,通過matcher.group(1)即可以檢索出標(biāo)簽內(nèi)容"helloworld"。 記住group(0)永遠(yuǎn)是整個(gè)"正則表達(dá)式"的匹配結(jié)果,即捕獲組0。在這個(gè)示例中,group(0)是"<xml>([a-z])</xml>"整串表達(dá)式的匹配結(jié)果。捕獲組在group(幾)取決于“()”在整個(gè)表達(dá)式中從左到右,從外到里的順序(這個(gè)可以自行驗(yàn)證)
后向引用
在檢索功能中我們知道了捕獲組的概念,正則表達(dá)式后面的子表達(dá)式可以使用前面的捕獲組,示例代碼:
@Test
public void test() throws IOException {
String str = "<xml>helloworld</xml>";
//“\\1”(實(shí)際上是“\1”,第一個(gè)\是轉(zhuǎn)義符號(hào))用來引用捕獲組1
String regex = "<(xml)>.*</\\1>";
Matcher matcher = Pattern.compile(regex).matcher(str);
//必須先調(diào)用matcher.find(),這樣才會(huì)將匹配的結(jié)果放到group中
if (matcher.find()) {
int count = matcher.groupCount();
for (int i = 0; i <= count; i++) {
System.out.println("matcher.group(" + i + "): " + matcher.group(i));
}
}
}
### 輸出結(jié)果
matcher.group(0): <xml>helloworld</xml>
matcher.group(1): xml
從上面代碼應(yīng)該很清楚 在正則表達(dá)式中可以通過"\i"的形式來引用捕獲組i 的內(nèi)容 即“xml“字符串。
注意事項(xiàng):
1.這里為什么叫后向引用呢,這是因?yàn)椤癨i”所引用的捕獲組i必須是在它前面所定義的,如果將上述表達(dá)式改為的"<\1>.*</xml>",就匹配不到了。
2.后向引用所引用的是"內(nèi)容",而非"表達(dá)式"。示例代碼:
將上面例子中的代碼改為:
@Test
public void test() throws IOException {
String str = "<xml>helloworld</html>";
String regex = "<([a-z]{3})>.*</\\1>";
Matcher matcher = Pattern.compile(regex).matcher(str);
//必須先調(diào)用matcher.find(),這樣才會(huì)將匹配的結(jié)果放到group中
if (matcher.find()) {
int count = matcher.groupCount();
System.out.println(count);
for (int i = 0; i <= count; i++) {
System.out.println("matcher.group(" + i + "): " + matcher.group(i));
}
}
}
### 輸出結(jié)果是空的
### 這是因?yàn)樵谄ヅ涞倪^程中,“([a-z]{3})”首先匹配到了“xml”,于是后面“\\1”就變成了“xml”,于是整個(gè)表達(dá)式其實(shí)變成了 "<([a-z]{3})>.*</xml>",這顯然是無法匹配到"<xml>helloworld</html>",所以整個(gè)表達(dá)式?jīng)]有匹配結(jié)果。
斷言
斷言的概念比較模糊,我的理解是斷言是特殊的限定符,用來對(duì)它所修飾的表達(dá)式添加匹配條件。示例代碼:
@Test
public void test() throws IOException {
String str = "hello2017";
//“X1(?=X2)” 這是斷言的一種寫法,表示表達(dá)式X1匹配成功的前提是它后面有能夠匹配表達(dá)式X2的內(nèi)容。
String regex = "hello(?=2017)";
Matcher matcher = Pattern.compile(regex).matcher(str);
//必須先調(diào)用matcher.find(),這樣才會(huì)將匹配的結(jié)果放到group中
if (matcher.find()) {
int count = matcher.groupCount();
for (int i = 0; i <= count; i++) {
System.out.println("matcher.group(" + i + "): " + matcher.group(i));
}
}
}
### 輸出
matcher.group(0): hello
Process finished with exit code 0
我們前面說過group(0)是整個(gè)表達(dá)式匹配的結(jié)果,但這里為什么不是hello2017而只輸出了hello呢? 斷言有點(diǎn)類似限定符,并不是匹配元素,所以整個(gè)表達(dá)式其實(shí)就是"hello",而斷言給這個(gè)表達(dá)式匹配增加了條件。在這個(gè)例子中,hello所匹配成功的條件必須是后面跟著2017的hello,也就是只能是hello2017中的hello。將上述str改成"hello2018",雖然hello字符串依然能夠配對(duì)hello,但是它后面跟的不是2017,不滿足斷言條件,因而匹配結(jié)果為空。
1.斷言是不消耗字符的,在上面的例子中,雖然2017參與了"hello(?=2017)"這個(gè)表達(dá)式的運(yùn)算,但它并沒有被消耗,仍舊可以被后面的表達(dá)式所匹配。示例代碼:
@Test
public void test() throws IOException {
String str = "hello2017";
String regex = "hello(?=2017)2";
Matcher matcher = Pattern.compile(regex).matcher(str);
//必須先調(diào)用matcher.find(),這樣才會(huì)將匹配的結(jié)果放到group中
if (matcher.find()) {
int count = matcher.groupCount();
for (int i = 0; i <= count; i++) {
System.out.println("matcher.group(" + i + "): " + matcher.group(i));
}
}
}
###輸出結(jié)果
matcher.group(0): hello2
從這個(gè)結(jié)果可以看出,2017雖然參與了"hello"字符串的匹配,但是并沒有被消耗掉,依然可以在后面的表達(dá)式"2"中使用,于是輸出結(jié)果是hello2
2.斷言不是捕獲組,前面所述的"()"的使用中說過"()"的內(nèi)容會(huì)被放入捕獲組,那么代碼中怎么區(qū)分是捕獲組還是斷言呢?其實(shí)就是?的作用,形如"(?=X)"由于加入了一個(gè)"?",因此代碼執(zhí)行時(shí)知道這是一個(gè)斷言,不需要放入捕獲組。
斷言分為4種:
| 表達(dá)式 | 定義 | 說明 |
|---|---|---|
| X1(?=X2) | 零寬度正先行斷言 | 僅當(dāng)表達(dá)式X1后出現(xiàn)X2時(shí),X1才能匹配成功。例如,“(hello)(?=\d)”匹配"hello1"成功,匹配"helloworld"不成功 |
| X1(?!X2) | 零寬度負(fù)先行斷言 | 與上述相反。 例“(hello)(?!\d)” 匹配"hello1"不成功,匹配"helloworld"成功。 |
| (?<=X2)X1 | 零寬度正后發(fā)斷言 | 僅當(dāng)表達(dá)式X1前面出現(xiàn)X2時(shí),X1才能匹配成功。例如,“(?<=\d)(hello)”匹配"88hello"成功,而匹配"aahello"不成功 |
| (?<!X) | 零寬度負(fù)后發(fā)斷言 | 僅當(dāng)表達(dá)式X1前面出現(xiàn)X2時(shí),X1才能匹配成功。例如,“(?<!\d)(hello)”匹配"ahello"成功,而匹配"88hello"不成功 |