復(fù)雜的用法不說,且說 Java 的正則表達(dá)式在匹配點(diǎn)(.) 和斜杠(\),表達(dá)式要分別寫作 \\. 和 \\\\, 難看些,不好理解。幸好還有些人記住了,匹配點(diǎn)(.) 或 {、[、(、?、$、^ 和 * 這些特殊符號(hào)要要前加雙斜框,匹配 \ 時(shí)要用四斜杠,這確實(shí)能讓你包走天涯的。那么為什么是這樣呢,不是一個(gè)斜杠、三個(gè)或更多呢,所以知其然還要知其所以然,這樣才能每次心中有數(shù),方能以一變 應(yīng)萬變。
首先,Java 的正則表達(dá)式語法說明參見:http://download.oracle.com/javase/6/docs/api/java/util/regex/Pattern.html。
用 最簡單的例子來說明問題吧,不創(chuàng)建 Pattern、Matcher 等對(duì)象,就看 String 對(duì)象的 replaceAll(String regex, String replacement),它第一個(gè)參數(shù)接收的就是一個(gè)正則表達(dá)式,我們可以在 IDE 里的調(diào)試器中看 "a.b".replaceAll(".","") 能不能得到你期望的結(jié)果。
先說為什么像點(diǎn)號(hào)(其他的特殊符號(hào)還有引號(hào)中的 "{、[、(、?、$、^ 和 *")前面要加雙斜杠,注意逗號(hào)(,) 不是這一類特殊字符,因?yàn)樗粫?huì)出現(xiàn)在中括號(hào)或花括號(hào)中。
顯然,如果直接執(zhí)行
1 | "a.b" .replaceAll( "." , "" ); //返回空字符串 |
得到的值不是你想要的結(jié)果,成空字符串了,因?yàn)辄c(diǎn)號(hào) "." 匹配了所有的字符,那要只匹配點(diǎn)號(hào)該如何呢,對(duì)的,雙斜杠
1 | "a.b" .replaceAll( "\\." , "" ); //對(duì)的,得到的是 ab |
那為什么是雙斜杠呢?這個(gè)很簡單,因?yàn)辄c(diǎn)號(hào)(.),是個(gè)特殊字符,所以它前面需要需要加個(gè)斜杠給它轉(zhuǎn)義,你要真只用一個(gè)斜杠來轉(zhuǎn)義,問題就來了,提示你:
Invalid escape sequence (valid ones are \b \t \n \f \r \" \' \\),也就是 Java 不認(rèn) \. 序列,所以還需要前面再加一道杠給其后的斜杠轉(zhuǎn)義出一個(gè)斜杠給點(diǎn)號(hào)(.) 用,也就是在 Java 字符串看起來是 “\\.”, 但作為正則表達(dá)式來說就是 “\.”,這于其語言的正則表達(dá)式是一致的。
也 就是說 Java 的正則表達(dá)式字符串有兩層次的意義,那就是 Java 字符串轉(zhuǎn)義出符合正則表達(dá)式語法的字符串,“\\.”, 轉(zhuǎn)義后交給正則表達(dá)式的就是 “\.”,這是符合傳統(tǒng)的。因?yàn)槲覀兤綍r(shí)字符串轉(zhuǎn)義后直接用于輸出,所以帶來不少誤解,這里的最終的正則表達(dá)式就是 Java 字符串的輸出。
細(xì)心的同志一定能看到在調(diào)試器里的顯示,看我們寫成的“\\.”, 在調(diào)試器里顯示的是 “\\\\.”,說的是如果我們要得到 “\\.”,這樣的輸出那 Java 的字符串就必須寫成 “\\\\.”, 兩個(gè)斜杠轉(zhuǎn)義出一個(gè)斜杠。
好 的,理解了上面的由來,我們來看看用四個(gè)斜杠來匹配一個(gè)斜杠的原理。主要原因是斜杠 \ 本身就是用于轉(zhuǎn)義別的字符的,當(dāng)然它的架子不是一般的大。因?yàn)檎齽t表達(dá)式串就是 Java 字符串的輸出,正常思維在正則表達(dá)式里匹配斜杠用 “\\”, 那么在 Java 程序里向控制臺(tái)輸出 “\\”雙斜杠該如何寫呢,對(duì)了,就是 “\\\\”,就這么簡單。
再一次從錯(cuò)誤里找下原因吧,假如我們寫成:
1 | "a\\b" .replaceAll( "\", " "); |
報(bào)什么錯(cuò)呢?String literal is not properly closed by a double-quote,因?yàn)樾备馨哑浜蟮碾p引號(hào)給轉(zhuǎn)義了,當(dāng)然字符串是未結(jié)束。再給它加個(gè)斜杠又如何呢?
1 | "a\\b" .replaceAll( "\\" , "" ); |
Java 的語法是通過了,但是執(zhí)行正則表達(dá)式不干了,你轉(zhuǎn)義出來的交給正則表達(dá)式的一個(gè)斜杠,叫它情何以堪,該去轉(zhuǎn)義誰呢?所以運(yùn)行時(shí)異常報(bào) An exception occurred: java.util.regex.PatternSyntaxException。
如果寫成三個(gè)斜杠呢?
1 | "a\\b" .replaceAll( "<a>\\\</a>" , "" ); //與單個(gè)斜杠是一樣的異常,掛單的斜杠把雙引號(hào)給轉(zhuǎn)義了 |
所以這樣推來推去也是該寫成
1 | "a\\b" .replaceAll( "<a>\\\\</a>" , "" ); |
對(duì)于正則表達(dá)式看到的就是 “\\”,哪種語言的正則表達(dá)式要的也是這個(gè),也是第一個(gè)斜杠轉(zhuǎn)義了第二個(gè),第三個(gè)轉(zhuǎn)義了第四個(gè),最終就是 “\\”,正則表達(dá)式里轉(zhuǎn)互相轉(zhuǎn)義一下就是 “\”了。
我原來理解還只是停留下轉(zhuǎn)義啊,再轉(zhuǎn)義的基礎(chǔ)上,隨著寫這篇才更加理解到其中要義的,才發(fā)現(xiàn),原來 Java 的正則表達(dá)式和其他語言的正則表達(dá)式語言是統(tǒng)一的。只要記住一點(diǎn),你要想的正則表達(dá)式字符串是什么,而正則表達(dá)式字符串就是 Java 字符串的的輸出結(jié)果,你就知道應(yīng)該怎么寫了。
最后來看下 Eclipse 調(diào)試器里僅匹配單個(gè)斜杠時(shí),IDE 里顯示的有多瘋狂:
這讓你體驗(yàn)到四個(gè)斜杠又何其多也。注:全文中的斜框標(biāo)準(zhǔn)意義上應(yīng)該叫做反斜杠,在此就不作全文替換了。