Java正則速成秘籍(一) - 招式篇

導(dǎo)讀

正則表達(dá)式是什么?有什么用?
正則表達(dá)式(Regular Expression)是一種文本規(guī)則,可以用來(lái)校驗(yàn)查找、替換與規(guī)則匹配的文本。

又愛(ài)又恨的正則

正則表達(dá)式是一個(gè)強(qiáng)大的文本匹配工具,但是它的規(guī)則實(shí)在很繁瑣,而且理解起來(lái)也頗為蛋疼,容易讓人望而生畏。

如何學(xué)習(xí)正則

剛接觸正則時(shí),我看了一堆正則的語(yǔ)義說(shuō)明,但是仍然不明所以。后來(lái),我多接觸一些正則的應(yīng)用實(shí)例,漸漸有了感覺(jué),再結(jié)合語(yǔ)義說(shuō)明,終有領(lǐng)悟。我覺(jué)得正則表達(dá)式和武俠修練武功差不多,應(yīng)該先練招式,再練心法。如果一開(kāi)始就直接看正則的規(guī)則,保證你會(huì)懵逼。
當(dāng)你熟悉基本招式(正則基本使用案例)后,也該修煉修煉心法(正則語(yǔ)法)了。真正的高手不能只靠死記硬背那么幾招把式。就像張三豐教張無(wú)忌太極拳一樣,領(lǐng)悟心法,融會(huì)貫通,少俠你就可以無(wú)招勝有招,成為傳說(shuō)中的絕世高手。

以上閑話可歸納為一句:學(xué)習(xí)正則應(yīng)該從實(shí)例去理解規(guī)則。

欲練神功,必先自宮

打開(kāi)秘籍:欲練神功,必先自宮!沒(méi)有蛋,也就不會(huì)蛋疼了。

Java正則速成秘籍分三篇:

展示Java對(duì)于正則表達(dá)式的支持。

介紹正則表達(dá)式的語(yǔ)法規(guī)則。

  • 見(jiàn)招拆招篇

從實(shí)戰(zhàn)出發(fā),介紹正則的常用案例。

本文是Java正則速成秘籍的招式篇。主要介紹JDK對(duì)于正則表達(dá)式的支持。
心法篇和見(jiàn)招拆招篇會(huì)陸續(xù)推出。

概述

JDK中的java.util.regex包提供了對(duì)正則表達(dá)式的支持。

java.util.regex有三個(gè)核心類(lèi):

  • Pattern類(lèi):Pattern是一個(gè)正則表達(dá)式的編譯表示。
  • Matcher類(lèi):Matcher是對(duì)輸入字符串進(jìn)行解釋和匹配操作的引擎。
  • PatternSyntaxException:PatternSyntaxException是一個(gè)非強(qiáng)制異常類(lèi),它表示一個(gè)正則表達(dá)式模式中的語(yǔ)法錯(cuò)誤。

注:需要格外注意一點(diǎn),在Java中使用反斜杠"\"時(shí)必須寫(xiě)成 "\\"。所以本文的代碼出現(xiàn)形如String regex = "\\$\\{.*?\\}" 其實(shí)就是"\$\{.*?\}",不要以為是畫(huà)風(fēng)不對(duì)哦。

Pattern類(lèi)

Pattern類(lèi)沒(méi)有公共構(gòu)造方法。要?jiǎng)?chuàng)建一個(gè)Pattern對(duì)象,你必須首先調(diào)用其靜態(tài)方法compile,加載正則規(guī)則字符串,然后返回一個(gè)Pattern對(duì)象。

Pattern類(lèi)一樣,Matcher類(lèi)也沒(méi)有公共構(gòu)造方法。你需要調(diào)用Pattern對(duì)象的matcher方法來(lái)獲得一個(gè)Matcher對(duì)象。

案例:Pattern和Matcher的初始化

Pattern p = Pattern.compile(regex);
Matcher m = p.matcher(content);

Matcher類(lèi)

Matcher類(lèi)可以說(shuō)是java.util.regex核心類(lèi)中的必殺技!

Matcher類(lèi)有三板斧(三類(lèi)功能):

  • 校驗(yàn)
  • 查找
  • 替換

下面我們來(lái)領(lǐng)略一下這三塊的功能。

校驗(yàn)文本是否與正則規(guī)則匹配

為了檢查文本是否與正則規(guī)則匹配,Matcher提供了以下幾個(gè)返回值為boolean的方法。

序號(hào) 方法及說(shuō)明
1 **public boolean lookingAt() ** 嘗試將從區(qū)域開(kāi)頭開(kāi)始的輸入序列與該模式匹配。
2 **public boolean find() **嘗試查找與該模式匹配的輸入序列的下一個(gè)子序列。
3 public boolean find(int start)重置此匹配器,然后嘗試查找匹配該模式、從指定索引開(kāi)始的輸入序列的下一個(gè)子序列。
4 **public boolean matches() **嘗試將整個(gè)區(qū)域與模式匹配。

如果你傻傻分不清上面的查找方法有什么區(qū)別,那么下面一個(gè)例子就可以讓你秒懂。

案例:lookingAt vs find vs matches

public static void main(String[] args) {
 checkLookingAt("hello", "helloworld");
 checkLookingAt("world", "helloworld");

 checkFind("hello", "helloworld");
 checkFind("world", "helloworld");

 checkMatches("hello", "helloworld");
 checkMatches("world", "helloworld");
 checkMatches("helloworld", "helloworld");
}

private static void checkLookingAt(String regex, String content) {
 Pattern p = Pattern.compile(regex);
 Matcher m = p.matcher(content);
 if (m.lookingAt()) {
 System.out.println(content + "\tlookingAt: " + regex);
 } else {
 System.out.println(content + "\tnot lookingAt: " + regex);
 }
}

private static void checkFind(String regex, String content) {
 Pattern p = Pattern.compile(regex);
 Matcher m = p.matcher(content);
 if (m.find()) {
 System.out.println(content + "\tfind: " + regex);
 } else {
 System.out.println(content + "\tnot find: " + regex);
 }
}

private static void checkMatches(String regex, String content) {
 Pattern p = Pattern.compile(regex);
 Matcher m = p.matcher(content);
 if (m.matches()) {
 System.out.println(content + "\tmatches: " + regex);
 } else {
 System.out.println(content + "\tnot matches: " + regex);
 }
}

輸出

helloworld lookingAt: hello
helloworld not lookingAt: world
helloworld find: hello
helloworld find: world
helloworld not matches: hello
helloworld not matches: world
helloworld matches: helloworld

說(shuō)明

regex = “world” 表示的正則規(guī)則是以world開(kāi)頭的字符串,regex = “hello” 和regex = “helloworld” 也是同理。

  • lookingAt方法從頭部開(kāi)始,檢查content字符串是否有子字符串于正則規(guī)則匹配。
  • find方法檢查content字符串是否有子字符串于正則規(guī)則匹配,不管字符串所在位置。
  • matches方法檢查content字符串整體是否與正則規(guī)則匹配。

查找匹配正則規(guī)則的文本位置

為了查找文本匹配正則規(guī)則的位置,Matcher提供了以下方法:

序號(hào) 方法及說(shuō)明
1 **public int start() **返回以前匹配的初始索引。
2 public int start(int group) 返回在以前的匹配操作期間,由給定組所捕獲的子序列的初始索引
3 public int end()返回最后匹配字符之后的偏移量。
4 public int end(int group)返回在以前的匹配操作期間,由給定組所捕獲子序列的最后字符之后的偏移量。
5 public String group()返回前一個(gè)符合匹配條件的子序列。
6 public String group(int group)返回指定的符合匹配條件的子序列。

案例:使用start()、end()、group() 查找所有匹配正則條件的子序列

public static void main(String[] args) {
 final String regex = "world";
 final String content = "helloworld helloworld";
 Pattern p = Pattern.compile(regex);
 Matcher m = p.matcher(content);
 System.out.println("content: " + content);

 int i = 0;
 while (m.find()) {
 i++;
 System.out.println("[" + i + "th] found");
 System.out.print("start: " + m.start() + ", ");
 System.out.print("end: " + m.end() + ", ");
 System.out.print("group: " + m.group() + "\n");
 }
}

輸出

content: helloworld helloworld
[1th] found
start: 5, end: 10, group: world
[2th] found
start: 16, end: 21, group: world

說(shuō)明

例子很直白,不言自明了吧。

替換匹配正則規(guī)則的文本

替換方法是替換輸入字符串里文本的方法:

序號(hào) 方法及說(shuō)明
1 public Matcher appendReplacement(StringBuffer sb, String replacement)實(shí)現(xiàn)非終端添加和替換步驟。
2 public StringBuffer appendTail(StringBuffer sb)實(shí)現(xiàn)終端添加和替換步驟。
3 **public String replaceAll(String replacement) ** 替換模式與給定替換字符串相匹配的輸入序列的每個(gè)子序列。
4 public String replaceFirst(String replacement) 替換模式與給定替換字符串匹配的輸入序列的第一個(gè)子序列。
5 public static String quoteReplacement(String s)返回指定字符串的字面替換字符串。這個(gè)方法返回一個(gè)字符串,就像傳遞給Matcher類(lèi)的appendReplacement 方法一個(gè)字面字符串一樣工作。

案例:replaceFirst vs replaceAll

public static void main(String[] args) {
 String regex = "can";
 String replace = "can not";
 String content = "I can because I think I can.";

 Pattern p = Pattern.compile(regex);
 Matcher m = p.matcher(content);

 System.out.println("content: " + content);
 System.out.println("replaceFirst: " + m.replaceFirst(replace));
 System.out.println("replaceAll: " + m.replaceAll(replace));
}

輸出

content: I can because I think I can.
replaceFirst: I can not because I think I can.
replaceAll: I can not because I think I can not.

說(shuō)明

replaceFirst:替換第一個(gè)匹配正則規(guī)則的子序列。

replaceAll:替換所有匹配正則規(guī)則的子序列。

案例:appendReplacement、appendTail和replaceAll

public static void main(String[] args) {
 String regex = "can";
 String replace = "can not";
 String content = "I can because I think I can.";
 StringBuffer sb = new StringBuffer();
 StringBuffer sb2 = new StringBuffer();

 System.out.println("content: " + content);
 Pattern p = Pattern.compile(regex);
 Matcher m = p.matcher(content);
 while (m.find()) {
 m.appendReplacement(sb, replace);
 }
 System.out.println("appendReplacement: " + sb);
 m.appendTail(sb);
 System.out.println("appendTail: " + sb);
}

輸出

content: I can because I think I can.
appendReplacement: I can not because I think I can not
appendTail: I can not because I think I can not.

說(shuō)明

從輸出結(jié)果可以看出,appendReplacementappendTail方法組合起來(lái)用,功能和replaceAll是一樣的。

如果你查看replaceAll的源碼,會(huì)發(fā)現(xiàn)其內(nèi)部就是使用appendReplacementappendTail方法組合來(lái)實(shí)現(xiàn)的。

案例:quoteReplacement和replaceAll,解決特殊字符替換問(wèn)題

public static void main(String[] args) {
 String regex = "\\$\\{.*?\\}";
 String replace = "${product}";
 String content = "product is ${productName}.";

 Pattern p = Pattern.compile(regex);
 Matcher m = p.matcher(content);
 String replaceAll = m.replaceAll(replace);

 System.out.println("content: " + content);
 System.out.println("replaceAll: " + replaceAll);
}

輸出

Exception in thread "main" java.lang.IllegalArgumentException: No group with name {product}
 at java.util.regex.Matcher.appendReplacement(Matcher.java:849)
 at java.util.regex.Matcher.replaceAll(Matcher.java:955)
 at org.zp.notes.javase.regex.RegexDemo.wrongMethod(RegexDemo.java:42)
 at org.zp.notes.javase.regex.RegexDemo.main(RegexDemo.java:18)
 at sun.reflect.NativeMethodAccessorImpl.invoke0(Native Method)
 at sun.reflect.NativeMethodAccessorImpl.invoke(NativeMethodAccessorImpl.java:62)
 at sun.reflect.DelegatingMethodAccessorImpl.invoke(DelegatingMethodAccessorImpl.java:43)
 at java.lang.reflect.Method.invoke(Method.java:498)
 at com.intellij.rt.execution.application.AppMain.main(AppMain.java:147)

說(shuō)明

String regex = "\\$\\{.*?\\}";表示匹配類(lèi)似${name}這樣的字符串。由于${ 、}都是特殊字符,需要用反義字符\來(lái)修飾才能被當(dāng)做一個(gè)字符串字符來(lái)處理。

上面的例子是想將 ${productName} 替換為 ${product} ,然而replaceAll方法卻將傳入的字符串中的$當(dāng)做特殊字符來(lái)處理了。結(jié)果產(chǎn)生異常。

如何解決這個(gè)問(wèn)題?

JDK1.5引入了quoteReplacement方法。它可以用來(lái)轉(zhuǎn)換特殊字符。其實(shí)源碼非常簡(jiǎn)單,就是判斷字符串中如果有\$,就為它加一個(gè)轉(zhuǎn)義字符\

我們對(duì)上面的代碼略作調(diào)整:

m.replaceAll(replace)改為m.replaceAll(Matcher.quoteReplacement(replace)),新代碼如下:

public static void main(String[] args) {
 String regex = "\\$\\{.*?\\}";
 String replace = "${product}";
 String content = "product is ${productName}.";

 Pattern p = Pattern.compile(regex);
 Matcher m = p.matcher(content);
 String replaceAll = m.replaceAll(Matcher.quoteReplacement(replace));

 System.out.println("content: " + content);
 System.out.println("replaceAll: " + replaceAll);
}

輸出

content: product is ${productName}.
replaceAll: product is ${product}.

說(shuō)明

字符串中如果有\$,不能被正常解析的問(wèn)題解決。

最后編輯于
?著作權(quán)歸作者所有,轉(zhuǎn)載或內(nèi)容合作請(qǐng)聯(lián)系作者
【社區(qū)內(nèi)容提示】社區(qū)部分內(nèi)容疑似由AI輔助生成,瀏覽時(shí)請(qǐng)結(jié)合常識(shí)與多方信息審慎甄別。
平臺(tái)聲明:文章內(nèi)容(如有圖片或視頻亦包括在內(nèi))由作者上傳并發(fā)布,文章內(nèi)容僅代表作者本人觀點(diǎn),簡(jiǎn)書(shū)系信息發(fā)布平臺(tái),僅提供信息存儲(chǔ)服務(wù)。

相關(guān)閱讀更多精彩內(nèi)容

友情鏈接更多精彩內(nèi)容