天天看點

Java正則速成秘籍(一)之招式篇導讀概述Pattern類Matcher類

導讀

正規表達式是什麼?有什麼用?

正規表達式(Regular Expression)是一種文本規則,可以用來校驗、查找、替換與規則比對的文本。

又愛又恨的正則

正規表達式是一個強大的文本比對工具,但是它的規則實在很繁瑣,而且了解起來也頗為蛋疼,容易讓人望而生畏。

如何學習正則

剛接觸正則時,我看了一堆正則的語義說明,但是仍然不明是以。後來,我多接觸一些正則的應用執行個體,漸漸有了感覺,再結合語義說明,終有領悟。我覺得正規表達式和武俠修練武功差不多,應該先練招式,再練心法。如果一開始就直接看正則的規則,保證你會懵逼。

當你熟悉基本招式(正則基本使用案例)後,也該修煉修煉心法(正則文法)了。真正的高手不能隻靠死記硬背那麼幾招把式。就像張三豐教張無忌太極拳一樣,領悟心法,融會貫通,少俠你就可以無招勝有招,成為傳說中的絕世高手。

以上閑話可歸納為一句:學習正則應該從執行個體去了解規則。

Java正則速成秘籍(一)之招式篇導讀概述Pattern類Matcher類

打開秘籍:欲練神功,必先自宮!沒有蛋,也就不會蛋疼了。

Java正則速成秘籍分三篇:

展示Java對于正規表達式的支援。

介紹正規表達式的文法規則。

從實戰出發,介紹正則的常用案例。

本文是Java正則速成秘籍的招式篇。主要介紹JDK對于正規表達式的支援。

概述

JDK中的

java.util.regex

包提供了對正規表達式的支援。

java.util.regex

有三個核心類:

  • Pattern類:

    Pattern

    是一個正規表達式的編譯表示。
  • Matcher類:

    Matcher

    是對輸入字元串進行解釋和比對操作的引擎。
  • PatternSyntaxException:

    PatternSyntaxException

    是一個非強制異常類,它表示一個正規表達式模式中的文法錯誤。

注:需要格外注意一點,在Java中使用反斜杠"\"時必須寫成

"\\"

。是以本文的代碼出現形如

String regex = "\\$\\{.*?\\}"

其實就是"\$\{.*?\}",不要以為是畫風不對哦。

Pattern類

Pattern

類沒有公共構造方法。要建立一個

Pattern

對象,你必須首先調用其靜态方法

compile

,加載正則規則字元串,然後傳回一個Pattern對象。

Pattern

類一樣,

Matcher

類也沒有公共構造方法。你需要調用

Pattern

對象的

matcher

方法來獲得一個

Matcher

對象。

案例:Pattern和Matcher的初始化

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

Matcher類

Matcher

類可以說是

java.util.regex

核心類中的必殺技!

Matcher

類有三闆斧(三類功能):

  • 校驗
  • 查找
  • 替換

下面我們來領略一下這三塊的功能。

校驗文本是否與正則規則比對

為了檢查文本是否與正則規則比對,Matcher提供了以下幾個傳回值為

boolean

的方法。

序号 方法及說明
1 public boolean lookingAt() 嘗試将從區域開頭開始的輸入序列與該模式比對。
2 public boolean find() 嘗試查找與該模式比對的輸入序列的下一個子序列。
3 public boolean find(int start)重置此比對器,然後嘗試查找比對該模式、從指定索引開始的輸入序列的下一個子序列。
4 public boolean matches() 嘗試将整個區域與模式比對。

如果你傻傻分不清上面的查找方法有什麼差別,那麼下面一個例子就可以讓你秒懂。

案例: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           

說明

regex = “world” 表示的正則規則是以world開頭的字元串,regex = “hello” 和regex = “helloworld” 也是同理。

  • lookingAt

    方法從頭部開始,檢查content字元串是否有子字元串于正則規則比對。
  • find

    方法檢查content字元串是否有子字元串于正則規則比對,不管字元串所在位置。
  • matches

    方法檢查content字元串整體是否與正則規則比對。

查找比對正則規則的文本位置

為了查找文本比對正則規則的位置,

Matcher

提供了以下方法:

public int start() 傳回以前比對的初始索引。
public int start(int group) 傳回在以前的比對操作期間,由給定組所捕獲的子序列的初始索引
public int end()傳回最後比對字元之後的偏移量。
public int end(int group)傳回在以前的比對操作期間,由給定組所捕獲子序列的最後字元之後的偏移量。
5 public String group()傳回前一個符合比對條件的子序列。
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           

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

替換比對正則規則的文本

替換方法是替換輸入字元串裡文本的方法:

public Matcher appendReplacement(StringBuffer sb, String replacement)實作非終端添加和替換步驟。
public StringBuffer appendTail(StringBuffer sb)實作終端添加和替換步驟。
public String replaceAll(String replacement) 替換模式與給定替換字元串相比對的輸入序列的每個子序列。
public String replaceFirst(String replacement) 替換模式與給定替換字元串比對的輸入序列的第一個子序列。
public static String quoteReplacement(String s)傳回指定字元串的字面替換字元串。這個方法傳回一個字元串,就像傳遞給Matcher類的appendReplacement 方法一個字面字元串一樣工作。

案例: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.           

replaceFirst:替換第一個比對正則規則的子序列。

replaceAll:替換所有比對正則規則的子序列。

案例: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.           

從輸出結果可以看出,

appendReplacement

appendTail

方法組合起來用,功能和

replaceAll

是一樣的。

如果你檢視

replaceAll

的源碼,會發現其内部就是使用

appendReplacement

appendTail

方法組合來實作的。

案例:quoteReplacement和replaceAll,解決特殊字元替換問題

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)           

String regex = "\\$\\{.*?\\}";

表示比對類似

${name}

這樣的字元串。由于

$

{

}

都是特殊字元,需要用反義字元

\

來修飾才能被當做一個字元串字元來處理。

上面的例子是想将

${productName}

替換為

${product}

,然而

replaceAll

方法卻将傳入的字元串中的

$

當做特殊字元來處理了。結果産生異常。

如何解決這個問題?

JDK1.5引入了

quoteReplacement

方法。它可以用來轉換特殊字元。其實源碼非常簡單,就是判斷字元串中如果有

\

$

,就為它加一個轉義字元

\

我們對上面的代碼略作調整:

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}.           

字元串中如果有

\

$

,不能被正常解析的問題解決。