正則表達式(二)

前言

在前面的《正則表達式(一)》的博文中已經記錄了正則表達式的基本語法,下面的內容主要是補充上一篇博文沒有介紹完全的一點內容以及記錄在Java語言中如何使用正則表達式進行字元串的判斷,提取資訊和替換資訊。

之所以使用Java語言,是因為JDK中已經內置好了正則表達式的庫,而且Java單元測試使用起來也非常方便,此外面向對象型的介面使用起來也更為舒適。

需要注意的是,在Java語言中,我們為了在字元串中表示反斜杠字元(\),我們需要輸入\\,因為在Java中\是一個用於轉義的字元,如\t\n等等,因此我們需要輸入兩個反斜杠來代表一個反斜杠字元了。例如表示匹配整數的表達式\d+,在Java中就需要寫成\\d+

一、非貪婪匹配

首先先看一個需求,我們的輸入是一串數字字元串,我們需要做的是它最後面的所有0字元和0前面的子串提取出來,例如:

  • "123000""123""000"
  • "110""11""0"
  • "1234""1234"""

我們很自然地可以寫出這樣的表達式:^(\d*)(0*)$

可是如果這樣寫匹配的結果和我們想像的是一樣嗎?

image-20210918000358088
image-20210918000510650

可以發現,分解的結果為:"123000""",與我們的想像完全不同。

產生這樣的結果的原因是:正則表達式默認使用貪婪匹配,任何一個規則,它總是儘可能多地向後匹配,因此,\d+總是會把後面的0包含進來。

而我們需要做的是使\d*盡量少匹配,而0*盡量多地匹配,這就需要使用到非貪婪匹配了,其含義即為使表達式盡量少地向後匹配。使用的方式是在某個量詞後面加上?,即原來的正則表達式需要修改為:^(\d*?)(0*)$,這樣我們的匹配結果為:

輸入串 結果
"123000" image-20210918001229183 image-20210918001256636
"110" image-20210918001414658 image-20210918001430408
"1234" image-20210918001506216 image-20210918001520476

可以發現現在得到的結果就是完全符合需求了。

二、Java中使用表達式判斷字元串

在Java中主要有三種使用方式:

法一(最常用)

使用String類中的成員方法boolean matches(String regex),使用方式如下所示:

@Test
public void t1() {
    String re = "^\\d+$";
    boolean isMatch = "123".matches(re);
    System.out.println(isMatch);
}

運行結果:

image-20210918002253873

其實點進去看String.matches的實現可以發現其內部也是使用正則表達式模組的方法進行判斷的,即第二種方法。String.matches內部如下圖所示:

image-20210918002517193

法二

使用Pattern類的靜態方法boolean matches(String regex, CharSequence input),使用方式如下:

@Test
public void t2() {
   String re = "^\\d+$";
   boolean isMatch = Pattern.matches(re, "123");
   System.out.println(isMatch);
}

其實再觀察Pattern.matches的源碼可以發現它是使用PatternMatcher共同完成的:

// Pattern.matches源碼
public static boolean matches(String regex, CharSequence input) {
    Pattern p = Pattern.compile(regex);
    Matcher m = p.matcher(input);
    return m.matches();
}

因此我們還可以使用這種方式來進行匹配。

法三

使用PatternMatcher共同完成

和上面源碼的方式一樣,即:

@Test
public void t3() {
    String re = "^\\d+$";
    Pattern pattern = Pattern.compile(re);
    Matcher matcher = pattern.matcher("123");
    boolean isMatch = matcher.matches();
    System.out.println(isMatch);
}

工作流程是:

  1. 編譯表達式,得到一個模式對象(Pattern對象)
  2. 使用Pattern對象對輸入字元串進行匹配,得到匹配結果集Matcher對象,在Matcher對象中包括了全部的匹配資訊,即包含所有匹配到的子串以及每個子串中的各個分組部分,例如:
    image-20210918001256636
  3. 使用Matcher對象提取需要的資訊

後面會發現無論是提取資訊還是替換資訊都是按照這個流程來走的。

三、Java中使用表達式提取資訊

提取資訊就需要使用到前面使用到的分組查詢的符號()了,即需要提取的內容使用圓括弧括起來,例如我們的需求還是上面那個將一個數字字元串的"0"串和前面的子串提取出來,我們的表達式和上面的一樣,為:^(\d*?)(0*)$

在Java中使用分組匹配也是按照上面判斷字元串是否匹配的【法三】一樣,需要分三步走,需要先後得到對應的Pattern對象和Matcher對象,然後我們需要使用到Matcher對象的成員方法String group(int group),即輸入分組索引,然後即可獲取到分組匹配到的內容,例如在可視化平台中我們使用這個表達式來匹配"123000",匹配的結果為:

image-20210918001229183
image-20210918001256636

可以看到group 1的結果為"123",group 2的結果為"000"

程式碼示例如下:

@Test
public void t4() {
    String re = "^(\\d*?)(0*)$";
    Pattern pattern = Pattern.compile(re);       //1.創建Pattern對象
    Matcher matcher = pattern.matcher("123000"); //2.創建Matcher對象
    if (matcher.matches()) {                     //3.判斷是否匹配
        String front = matcher.group(1);         //4.使用group方法提取
        String end = matcher.group(2);
        System.out.printf("front: \"%s\", end: \"%s\"%n", front, end);
    }
}

運行結果:

image-20210918110114557

四、匹配多處資訊

例如當前這樣的需求:給出一段輸入菜單字元串,提取出裡面所有的金額數據並統計總價格。

例如輸入為:"鹹魚茄子煲:20.5元,手撕包菜:13元,米飯:2元,紅燒肉:22.5元"

因此我們需要使用正則表達式匹配多處資訊並將它們提取出來,我們需要匹配的是小數,寫出表達式為:(\d+(\.\d*)?)

輸入到可視化平台中的效果:

image-20210918110818120

可以發現,這個模式將所有的價格數字字元串都匹配到了,剩下的工作是我們如何匹配這多處資訊並且將資訊提取出來呢?

這個過程需要使用到Matcher對象的boolean find()成員方法,這個方法的作用是每次往下找到一個匹配的子串,如果找到了則返回true,否則返回false,因此我們可以使用這個方法不斷向下迭代直到匹配完所有的價格。提取資訊的方法與【三】中介紹的還是一樣的。

程式碼示例如下:

@Test
public void t5() {
    String re = "(\\d+(\\.\\d*)?)";
    Pattern pattern = Pattern.compile(re);
    Matcher matcher = pattern.matcher("鹹魚茄子煲:20.5元,手撕包菜:13元,米飯:2元,紅燒肉:22.5元");
    float result = 0;
    while (matcher.find()) {
        String moneyStr = matcher.group(1);
        result += Float.parseFloat(moneyStr);
        System.out.println(moneyStr);
    }
    System.out.println("result: " + result);
}

執行結果:

image-20210918111921852

五、使用表達式替換字元串

法一(常用)

使用String類的成員函數String replaceAll(String regex, String replacement),其中

  1. 參數1為匹配模式
  2. 參數2位用於替換的字元串

例如使用正則表達式將字元串中所有的空白字元替換為,,此時的匹配模式為\s+,程式碼實例如下:

@Test
public void t6() {
    String str = "123 wej  1jwe         315";
    String result = str.replaceAll("\\s+", ", ");
    System.out.println("result: " + result);
}

運行結果:

image-20210918114919404

法二

第二種方法既是使用String類的成員函數replaceAll的實現方式,即:

//replaceAll源碼
public String replaceAll(String regex, String replacement) {
    return Pattern.compile(regex).matcher(this).replaceAll(replacement);
}

示例如下:

@Test
public void t7() {
    String str = "123 wej  1jwe         315";
    Pattern pattern = Pattern.compile("\\s+");
    Matcher matcher = pattern.matcher(str);
    String result = matcher.replaceAll(", ");
    System.out.println("result: " + result);
}