
?正則表達式能夠定義一個字符串的格式,讀者也可以理解為定義一個字符串的結構特征,例如定義一個電子郵件地址的結構特征等。而書寫正則表達式需要使用一些有特殊含義的符號,專業上把這種有特殊含義的符號稱為“通配符”這些通配符有的代表數字,有的代表字母,因此使用通配符可以很容易的定義出如連續的3個數字、連續的8個字母這樣的特定格式,本小節將講解如何使用這些通配符。
Java語言中表示字符串的String類的很多方法都以正則表達式作為參數,正因如此,調用這些方法時需要特別注意正則表達式的通配符,否則會導致程序運行的運行結果與預期不相符,請看下面的【例16_01】。?
【例16_01通配符】
【資料圖】
Exam16_01.java?
public class Exam16_01 { public static void main(String[] args) { String str = "he.llo world."; String result = str.replaceAll(".","-"); System.out.println(result); }}
本例中,程序員希望調用String類的replaceAll()方法把字符串中的“.”全部替換為“-”,但程序的運行結果如圖16-1所示。?
圖16-1【例16_01】運行結果?
從圖16-1可以很明顯的看出程序的運行結果并沒有達到預期,這是因為replaceAll()方法的第一個參數的名稱是regex,這個參數的名稱表示它是一個正則表達式,而在正則表達式中“.”是一個通配符,它表示除換行符(\n)之外的所有字符,因此程序中replaceAll()方法實際上是把字符串“he.llo world.”中的每一個字符都替換為“-”,因此才出現了圖16-1所示的運行結果。正則表達式中的通配符有很多,如表16-1所示。?
表16-1 正則表達式的通配符?
符號? | 含義? |
\d? | 匹配一個數字字符,既0~9當中的一個? |
\D? | 匹配一個非數字字符? |
\w? | 匹配任意一個數字、字母和下劃線? |
\W? | 匹配所有\w不匹配的字符? |
\s? | 匹配空白字符,包括空格、制表符、換頁符等? |
\S? | 匹配所有\s不匹配的字符? |
.? | 匹配除換行符(\n)之外的所有字符,1個.僅能匹配1個字符? |
專業上把希望完成匹配的子字符串稱為“目標字符串”,按照表16-1所介紹的通配符的功能,程序員可以定義任意格式的目標字符串,下面的【例16_02】展示了如何使用特殊符號完成目標字符串的替換操作。?
【例16_02 替換目標字符串1】
Exam16_02.java?
public class Exam16_02 { public static void main(String[] args) { String str = "3ast1dfj6asdjfpe2utmgnb3qweanki"; //把數字以及之后的字母a替換為-- String result = str.replaceAll("\\da","--"); System.out.println( result); }}
【例16_02】中,代表數字的“\d”被寫作“\\d”,這是因為“\”是轉義字符的標志,因此“\”出現在字符串中要被寫作“\\”。程序中所定義的目標字符串格式是“數字與字母a的組合”,使用正則表達式書定義這個目標字符串的格式就是“\\da”。可以看出:str中包含的“3a”和“6a”都符合這個格式,這樣的組合會被統一替換成“--”。【例16_02】的運行結果如圖16-2所示。?
圖16-2【例16_02】運行結果?
從圖16-2可以看出:只有數字與字母a的組合被替換為--,而數字與其他字母的組合都沒有被替換。需要強調:“\d”代表的是一個數字字符,而不能代表一個數學意義上的數字,例如數學意義上的數字13實際上是兩個數字字符組成的,因此“\d”不能代表數字13。?
表16-1中的“\w”能匹配數字、字母和下劃線,其中字母不分大小寫,都能完成匹配。“\W”的意義恰好與“\w”相反,它匹配的是“\w”所不能匹配的其他字符。下面的【例16_03】展示了“\w”和“\W”的效果。?
【例16_03 替換目標字符串2】
Exam16_03.java?
public class Exam16_03 { public static void main(String[] args) { String str = "你1好2*#abc以及A_B&&"; String result1 = str.replaceAll("\\w","-");//① String result2 = str.replaceAll("\\W","@");//② System.out.println(result1); System.out.println(result2); }}
【例16_03】中,語句①是把字符串str中的所有數字、字母和下劃線都替換為“-”,而語句②則是把str中那些不能被“\w”所匹配的字符全部替換為“@”,【例16_03】的運行結果如圖16-3所示。?
圖16-3【例16_03】運行結果?
從圖16-3可以看出:漢字不能被“\w”所匹配。?
在眾多通配符中,只有“.”不是以“\”開頭的,如果希望在完成匹配的過程中把“.”當做一個普通字符,那么就需要把“.”寫成“\\.”,下面的【例16_04】展示了把“.”還原為一個普通字符的方法。?
【例16_04 還原特殊符號】
Exam16_04.java?
public class Exam16_04 { public static void main(String[] args) { String str = "he.llo world."; //把str中的.替換為- String result = str.replaceAll("\\.","-"); System.out.println(result); }}
【例16_04】中,語句①定義的目標字符串格式為“\\.”,這就表示要匹配“.”而不是任意一個字符。【例16_04】的運行結果如圖16-4所示。?
圖16-4【例16_04】運行結果?
16.1.1小節介紹的這些通配符當中有的能夠匹配數字字符,有的可以匹配空白字符,它們各自都有一定的作用。但是,如果程序員想匹配某些特定的字符,例如只匹配abcd這四個字母中的一個,這就需要用到正則表達式的自定義通配符。自定義通配符由一對中括號([])定義,也就是說程序員只要在一對中括號中寫上一些字符,這些字符中的任意一個都會被當作匹配目標。下面的【例16_05】展示了自定義通配符的用法。?
【例16_05 自定義通配符】
Exam16_05.java?
public class Exam16_05 { public static void main(String[] args) { String str = "abcd7ab3exga]8wgqea%6qmv-ku"; String result = str.replaceAll("[abcd]","*"); System.out.println(result); }}
【例16_05】中,replaceAll ()方法的第一個參數是“[abcd]”,這是一個自定義的通配符,它能匹配abcd中任意一個字母。replaceAll ()方法的第二個參數是“*”,這樣的話字符串str中的a、b、c、d這些字母都會被替換為*,【例16_05】的運行結果如圖16-5所示。?
圖16-5【例16_05】運行結果?
從圖16-5可以看出:字符串中的a、b、c、d這些字母都被替換為了*。需要強調:[abcd]代表a、b、c、d中的任意一個字母不是代表“abcd”這個字符串,正因如此,字符串str開頭的abcd被替換為4個“*”而不是一個“*”。?
如果自定義通配符中定義的字符范圍是一組連續的字符,程序員也可以只寫出這組字符的開頭和結尾,中間用一個“-”把開頭和結尾連接起來,例如“[abcd]”也可以寫成“[a-d]”。需要注意:在用“-”連接兩個字符的時候,一定要把編碼小的字符寫在左邊,而把編碼大的字符寫在右邊,否則在程序運行時會拋出異常。如果自定義通配符中的字符范圍是兩組連續的字符,也可以直接把兩組連續的字符都寫出來,中間不用任何符號分隔這兩個組,例如希望自定義的通配符能夠匹配a、b、c、d以及x、y、z,就可以把自定義通配符寫為“[a-dx-z]”。如果兩個組中間如果出現了一些其他字符也是可以的,比如“[a-dqvx-z]”,這個自定義通配符能夠匹配a、b、c、d、q、v、x、y、z這幾個字符。?
如果希望自定義通配符也能匹配“-”這個字符,該怎么定義這個通配符呢?這時候要分兩種情況討論,如果表達式引擎能夠認定這個“-”是一個普通字符,那么就直接在方括號中寫上“-”本身就可以,否則就需要把“-”寫成轉義字符的形式。例如有自定義通配符“[a-]”,在這個自定義通配符中,由于“-”后面沒有任何字符,表達式引擎會把“-”當作普通字符,而這個自定義的通配符可以匹配“a”和“-”。如果希望自定義通配符能夠匹配“a”、“-”和“d”,這種情況下假設把自定義通配符寫為“[a-d]”,那么表達式引擎會認為這個自定義通配符能夠匹配a、b、c、d,與期望的語義不同,因此需要用轉義字符的形式把自定義通配符寫為“[a\\-d]”。?
自定義通配符由[]定義,如果需要讓自定義通配符匹配“[”和“]”該怎么辦呢?表達式引擎處理這兩個字符的策略并不一樣。表達式引擎允許“]”直接出現在自定義通配符中,例如“[]x]”這個自定義通配符可以匹配“]”或“x”。但是需要注意:如果把剛才的自定義通配符寫為“[x]]”,在這種情況下“[x]]”就已經不再是一個單獨的自定義通配符了,而是變成了自定義通配符和“]”的組合。這個組合中,只有“[x]”是自定義通配符,而這個自定義通配符只能匹配“x”,而“]”也只能匹配“]”,所以“[x]]”這個組合匹配的是“x]”而不是“]”或“x”。因此,如果希望自定義通配符中包含“]”,就應該把它寫作最左邊,否則就要用轉義字符的形式書寫它。?
表達式引擎對于“[”的處理策略是干脆不允許它直接出現在自定義通配符當中,必須以轉義字符的形式出現。例如希望定義一個能夠匹配“[”或“x”,需要把自定義通配符寫為“[\\[x]”。?
如果程序員定義的通配符是希望它能夠匹配某些字符以外的其他字符,那么就需要用“[^]”的形式來定義,例如希望自定義的通配符能夠匹配除a、b、c、d這四個字符以外的其他任何字符就可以把自定義通配符寫為“[^abcd]”。讀者可以嘗試把【例16_05】中語句①的“[abcd]”改成“[^abcd]”后再次運行程序并觀察運行效果。此外還可以看出:在正則表達式中“^”也是一個特殊符號,如果希望表示其原本語義,也需要用轉義字符的形式書寫。?
正則表達式中的特殊符號一般都是通配符,但有時候需要把字符串中出現的這些特殊符號替換成其他字符,此時就需要用轉義字符的形式書寫這些特殊符號,例如:?
String str = "..a..**a**";?String result = str.replaceAll("\\.\\.a\\.\\.","@");?
上面這段代碼是把字符串str中的“..a..”替換為“@”,由于“.”在正則表達式中可以匹配除換行符外的任意字符,因此需要在replaceAll()方法的第一個參數中以轉義字符的形式書寫,也就是寫為“\\.”,如果不這樣寫,那么str中的“**a**”也會被替換為“@”。讀者不難發現:如果一個正則表達式中大量出現轉義字符,會使正則表達式中出現大量“\\”從而導致表達式變得過于繁瑣。為解決這個問題,正則表達式引入了“特殊字符失效區”。所謂“特殊字符失效區”是一片區域,在這片區域中特殊字符將被還原成普通字符。?
特殊字符失效區以“\Q”作為開頭,以“\E”作為結尾,在實際書寫時要寫為“\\Q”和“\\E”,正則表達式中“\Q”和“\E”之間出現的特殊字符都會被表達式引擎當作普通字符,下面的【例16_06】展示了特殊字符失效區的作用。?
【例16_06 特殊字符失效區】
Exam16_06.java
public class Exam16_06 { public static void main(String[] args) { String str = "..a..**a**"; String result1 = str.replaceAll("\\Q..a..\\E","@");// ① String result2 = str.replaceAll("..a..","@");//② System.out.println(result1); System.out.println(result2); }}
【例16_06】的運行結果如圖16-6所示。?
圖16-6【例16_06】運行結果?
從圖16-6可以很明顯的看出:在語句①設置的特殊字符失效區中,通配符被還原成了普通字符。而語句②沒有設置特殊字符失效區,通配符“.”匹配任意字符,所以導致str中的“**a**”也會被替換為“@”。?
通配符能夠表示某一類型的字符,例如“\d”表示數字。如果希望把字符串中連續的3個數字替換為一個“*”,要把代碼寫為:?
String s = "123a234b";?String r = s.replaceAll("\\d\\d\\d","*");?
以上代碼雖然能達到目的,但假如需求發生了變化,要求在一個字符串中找到連續的100個數字,則需要把“\d”寫100遍,這樣不僅僅非常麻煩,而且可讀性極差。程序員很難檢查“\d”的數量是不是正確。這種情況下就要求以一種更合理的方式定義某類特定字符出現的次數。?
正則表達式以一對大括號定義特定字符的出現次數,程序員在大括中寫上特定字符出現次數即可,例如希望把連續出現3個數字替換為一個“*”,就可以把代碼寫為:?
String s = "123a2345b";?String r = s.replaceAll("\\d{3}","*");?
如果字符串中連續出現的數字超過3個,則把每3個數字組成一組,不夠3個數字的部分不做替換。下面的【例16_07】展示了如何在正則表達式中定義特定字符的出現次數?
【例16_07 定義出現次數1】
Exam16_07.java?
public class Exam16_07 { public static void main(String[] args) { String str = "123a2345b"; String result = str.replaceAll("\\d{3}","*"); System.out.println(result); }}
【例16_07】的運行結果如圖16-7所示。?
圖16-7【例16_07】運行結果?
【例16_07】中的字符串str包含“123”和“2345”兩個子子字符串,它們當中的數字都達到了3個,在進行替換時會把“123”替換為一個“*”,而“2345”中“234”恰好是連續的3個數字,它們會被替換我一個“*”,而剩下的“5”不是3個連續的數字,因此不能被替換。?
有的時候,并不一定要求被匹配的字符一定是恰好出現多少次,而是把出現的次數規定為一個范圍,比如說,希望數字連續出現3次到5次都能匹配成功,這種情況下可以在大括號中寫兩個數字,這兩個數字之間用逗號隔開,用這兩個數字來表示特定字符最少出現多少次,以及最多出現多少次,例如:?
String str = "123a2345b";?String result = s.replaceAll("\\d{3,5}","*");?
下面的【例16_08】展示了如何把出現次數定義成一個范圍以及表達式引擎如何完成匹配。?
【例16_08定義出現次數2】
Exam16_08.java?
public class Exam16_08 { public static void main(String[] args) { String str = "123a234567b"; String result = str.replaceAll("\\d{2,3}","*"); System.out.println(result); }}
【例16_08】中的replaceAll()方法希望把2~3個連續的數字替換為一個“*”。可以看出:str包含“123”,如果按照2個數字進行匹配,則“12”會被替換為一個“*”,“3”會被保留,而如果按照3個數字進行匹配,則“123”會被替換為一個“*”。同理,如果按照2個數字進行匹配,“234567”會被替換為3個“*”,但按照3個數字進行匹配“234567”會被替換為2個“*”。【例16_08】的運行結果如圖16-8所示。?
圖16-8【例16_08】運行結果?
從圖16-8可以看出:在完成匹配時都是按3個字符進行匹配的,因此可以得出結論:當規定了出現次數是一個范圍時,表達式在每次進行匹配都盡量把更多的目標字符包含進來,也可以理解為:盡量用更多的字符來完成一次匹配。?
如果把大括號中第二個數字去掉,則表示特定字符出現的次數沒有上限,例如:?
String str = "123a2345b";?String result = s.replaceAll("\\d{3, }","*");?
以上這段代碼表示要把連續的3個或以上的數字替換為一個“*”,字符串str中的“123”和“2345”都符合這個替換條件,因此它們都會被替換為“*”。?
定義出現次數也有一些有特定含義的符號,如表16-2所示。?
表16-2 表示出現次數的符號?
符號? | 意義? |
? ? | 出現0次或者1次,相當于{0,1}? |
*? | 至少出現0次,相當于{0,}? |
+? | 子字符串至少出現1次,相當于{1,}? |
()? | 括號中的表達式作為整體? |
|? | |兩側的表達式以或者關系存在? |
下面的【例16_09】展示了表16-2中所列符號的作用。?
【例16_09定義出現次數3】?
Exam16_09.java?
public class Exam16_09 { public static void main(String[] args) { String str1 = "12ab34c"; String str2 = "ababbbc"; String str3 = "Tom and Jack are my friends,but Perter is not."; String result1 = str1.replaceAll("\\d?","*");//① String result2 = str1.replaceAll("\\d*","*");//② String result3 = str1.replaceAll("\\d+","*");//③ String result4 = str2.replaceAll("ab+","*");//④ String result5 = str2.replaceAll("(ab)+","*");//⑤ String result6 = str3.replaceAll("Tom|Jack","*");//⑥ System.out.println(result1); System.out.println(result2); System.out.println(result3); System.out.println(result4); System.out.println(result5); System.out.println(result6); }}
【例16_09】的運行結果如圖16-9所示。?
圖16-9【例16_09】運行結果?
下面對每條語句的運行結果進行逐一解釋。語句①中,?表示出現次數為0或1。str1開頭的1和2恰好都是一個數字字符,它們完成匹配,因此1和2就被替換成了“*”。在2的后面是字母a,字母a不是數字,當然不能完成匹配。在數字2和字母a之間什么都沒有,但表達式引擎認為:什么都沒有就等同于出現了0個數字,所以也能完成匹配,因此數字2和字母a之間就又出現了一個可以匹配的字符,雖然這個字符事實上是不存在的,但它仍然被替換成了一個“*”,這樣,表示替換結果的result1的開頭就出現了3個星號。按照同樣的道理,就可以解釋為什么字母c之前只有兩個數字卻被替換為3個“*”。此外,在字母a和b之間本來什么都沒有,但最終result1中ab之間卻出現了一個“*”。字符串str1的結尾本來是一個字母c,在result1中字母c的后面也出現了一個“*”,就是因為表達式引擎認為字母c的后面什么都沒有,就是出現了0個數字。事實上,一個字符的后面不是數字,表達式引擎都會認為這個字符后面有0個數字,所以都會把它替換成星號。?
語句②中,*表示出現次數為0到正無窮。由于每次匹配盡量包含更多的字符,所以字符串開頭的12會被當作一個整體完成一次匹配,12與字母a之間沒有字符,并且a不是數字,表達式引擎認為這里有0個數字,也會完成一次匹配。這樣,當完成替換之后,字母a的前面就會有兩個“*”。同理,字母a后面的34會被替換為一個星號,數字4和字母b之間也有0個字符,它也會被替換成“*”,這樣完成替換之后字母b后面就會有兩個“*”。再往后,字母c的后面也都有0個字符,都會被替換成一個“*”。?
語句③中,+表示出現1到正無窮,只有出現數字的地方才會被替換成“*”,而兩個字母之間的“縫隙”不會被替換成一個“*”,因此表示運行結果的result3中只有數字出現的地方才會被替換為“*”,并且連續出現的數字會被作為整體替換為一個“*”。?
語句④中的正則表達式是“ab+”,需要注意:+只用來描述b的出現次數,并不負責描述前面的字母a,因此“ab+”表示“a后面至少有1個b”,因此str2中的“ab”和“abbb”都會被替換為一個“*”。?
語句⑤中的()表示一個整體,因此“(ab)+”表示“ab”出現1次或多次,而str2中的“abab”恰好能與“(ab)+”匹配,所以它被替換為“*”。?
語句⑥中的|表示“或者”的意思,因此語句中的“Tom|Jack”表示“Tom或Jack”會被替換為*。
本文字版教程還配有更詳細的視頻講解,小伙伴們可以??點擊這里??觀看。