正則表達式入門

每種語言對正則的支持略有不一樣, 這裏咱們主要說的是Java對正則表達式的支持.html

什麼是正則表達式

正則表達式(Regular Expression), 常簡寫爲regex, 是定義搜索模式的一組字符串 (用一組字符描述了一個字符串規則). 一般用於字符串查找和字符串的校驗.java

小試牛刀

判斷手機號字符串的正則表達式.
籠統講手機號的特色: 以1開頭, 後面是10位數字. 因此咱們寫一個簡單判斷是否知足這個條件的正則表達式:正則表達式

^1\d{10}$

其中, ^表明起始位置, $表明結尾位置. \d 表明是一個數字字符, 後面大括號裏面的數字, 表明它前面的元素會出現10次.express

咱們能夠簡單用一行代碼驗證下:api

System.out.println("13436417560".matches("^1\\d{10}$"));

注, 因 Java 代碼裏面反斜槓起轉義做用, 例如 \t 表明 tab, 因此在 Java 代碼裏, 兩個反斜槓才能表示出一個反斜槓.oracle

正則表達式中經常使用字符

正則表達式中的轉義

正則表達式經過反斜槓來進行轉義. 例如:測試

點字符.表明任意字符, 而\.表明真正的小數點.
\d 表明一個數字字符, 而 \\d 表明一個反斜槓和一個字符d.
\\表明一個反斜槓字符.fetch

經常使用字符

經常使用字符 意義
x 普通字符 x
^ 表示字符串起始位置
$ 表示字符串結尾位置
\\ 反斜槓字符
\t tab字符
\n 換行字符
\r 回車字符
. 表明任意字符(默認不會匹配\r和\n, 須要配置才匹配)
[abc] 方括號表示其中的任意字符. 方括號中任意字符(多是a 或 b 或 c)
[^abc] 不在方括號中的任意字符
[a-zA-Z] 任意字母, 包括大寫和小寫
\d 任意數字 [0-9]
\D 任意非數字 [^0-9]
\s 任意空字符 [ \t\n\x0B\f\r]
\S 任意非空字符 [^\s]
\w 任意組成單詞字符 [a-zA-Z_0-9]
\W 任意非組成單子字符 [^\w]
x? 表明x字符不存在或者存在1次(最多存在1次)
x* 表明x字符不存在或者存在任意次
x+ 表明x字符至少存在1次
x{5} x字符存在5次
x{3,5} x字符存在3到5次

正則表達式應用場景簡介

字符串查找

正則表達式查找能夠解決普通查找只能根據"特定文本"查找的問題.atom

示例:.net

假設如今有一堆 JSON 日誌, 咱們須要查出 cabinX 開頭的日誌(X後面只可能跟數字): 假設日誌以下:

..."from":"PEK", "cabin":"Y"...
..."from":"SHA", "cabin":"X"...
..."from":"XIY", "cabin":"X2"...
..."from":"XIY", "cabin":"Y2"...

咱們提取的正則表達式是:

cabin":"X\d*"

字符串校驗

使用正則表達式能夠校驗文本是否符合必定規範. 例如一開始提到的手機號格式校驗. 還有郵箱格式校驗等. 也能夠對身份證格式進行簡單的校驗.

字符串提取

咱們可使用正則表達式將文本中的一部分提取出來. 主要用到了正則表達式中的 capturing group 概念.

capturing group

正則表達式中可使用小括號將表達式分紅多個捕獲組. 分組之後, 能夠經過捕獲組號, 取出對應匹配的內容. 經過數左半括號便可得到組號. 例如:

((A)(B(C)))

上面正則表達式對應的捕獲組信息以下: 捕獲組號 | 對應內容 ---|--- 1 | ((A)(B(C))) 2 | (A) 3 | (B(C)) 4 | (C)

第0組老是表明整個正則表達式.

示例代碼以下:

Pattern pattern = Pattern.compile("((A)(B(C)))");
Matcher matcher = pattern.matcher("XXXABCDEF");
if (matcher.find()) {
    for (int i = 0; i <= matcher.groupCount(); i++) {
        System.out.println("group " + i + " : " + matcher.group(i));
    }
}

輸出爲:

group 0 : ABC
group 1 : ABC
group 2 : A
group 3 : BC
group 4 : C

其餘應用示例:

從大量日誌中, 獲取天天的車次號, 並計數.

named-capturing group

在Java中, Java7之後, 能夠爲 capturing-group 命名. 取的時候能夠根據名字來取. 命名示例以下:

(?<Name>[Pp]attern)

經過在普通 capturing-group 的最前面, 添加 ?<xxx> 來指定名字. 使用示例以下:

public static String fetchOneNamedGroup(String src, String regex, String groupName) {
    Pattern pattern = Pattern.compile(regex, Pattern.DOTALL);
    Matcher matcher = pattern.matcher(src);
    if (matcher.find()) {
        return matcher.group(groupName);
    }
    return null;
}

public static void main(String[] args) throws Exception {
    String sourceType = fetchOneNamedGroup("sourceType:PC,name:test", "sourceType:(?<sourceType>\\w+)",
            "sourceType");
    System.out.println(sourceType);
}
// 結果是"PC"

注意, capturing-group 的名字必須知足條件: [A-Za-z][A-Za-z0-9]*

貪婪匹配 vs 最小匹配

咱們要從Hello World中獲取H和l以及之間的字符 Hel. 因而, 咱們寫了以下正則表達式:

H.*l

可是結果卻並非咱們想要的:

Hello Worl

仔細分析發現, Hello Worl 這個結果也一樣知足咱們的正則表達式.

如今問題在於咱們能夠認爲 Hel 是咱們要的結果(最小匹配), 也能夠認爲 Hello Worl 是第一個H和最後一個l之間的字符(貪婪匹配).

正則表達式默認狀況下是貪婪匹配模式. 想要最小匹配, 只須要在貪婪匹配模式符後面加一個?, 便可轉化爲最小匹配.

例如:

咱們使用H.*?l的結果就是最小匹配:

Pattern pattern = Pattern.compile("H.*?l");
Matcher matcher = pattern.matcher("Hello World");
if (matcher.find()) {
    System.out.println(matcher.group(0));
}
// 結果: Hel

Java裏面的正則表達式

Java裏面經過 java.util.regex.Pattern 實現了對正則的支持.

Java裏面的正則和grep命令裏面的就略有不一樣, 例如在Java裏面, 星號 * 表明0個或多個, 而在 grep 裏面, * 表明任意內容.

.* 多行匹配

默認狀況下, . 在Java不會匹配換行符. 也就是 .* 只能匹配一行. 若是須要經過 .* 匹配多行狀況, 能夠開啓 DOTALL mode.

示例:

public static String fetchGroup1(String src, String regex) {
    Pattern pattern = Pattern.compile(regex, Pattern.DOTALL);
    Matcher matcher = pattern.matcher(src);
    if (matcher.find()) {
        return matcher.group(1);
    }
    return null;
}

String 中正則表達式相關方法

String 中有幾個直接正則表達式的方法, 使用方便, 同時能夠用於測試.

boolean matches (String regex)
String replaceAll (String regex, String replacement)
String replaceFirst (String regex, String replacement)
String[] split (String regex)
String[] split (String regex, int limit)

注意, replace, replaceFirst 和 replaceAll 的區別.

痛點

Java字符串經過反斜槓來標示特殊字符, 而正則表達式一樣經過反斜槓來專業, 致使代碼中的正則表達式可讀性極差.

正則表達式中的特殊結構體(不經常使用)

(?:pattern) 非獲取匹配

匹配pattern但不獲取匹配結果, 也就是說這是一個非獲取匹配. 儘管匹配, 將此group匹配結果不保存, 不做爲最終結果返回.

示例:

https://stackoverflow.com/questions/tagged/regex
正則表達式: (https?|ftp)://([^/\r\n]+)(/[^\r\n]*)?
匹配結果:
Match "https://stackoverflow.com/questions/tagged/regex"
     Group 1: "https"
     Group 2: "stackoverflow.com"
     Group 3: "/questions/tagged/regex"
若是並不在乎用的什麼協議, 可是 http/https/ftp 協議是使用括號括起來並用了|鏈接符. 若是想把這個group的結果給捨棄了, 則經過費獲取匹配:
(?:https?|ftp)://([^/\r\n]+)(/[^\r\n]*)?
結果是:
Match "https://stackoverflow.com/questions/tagged/regex"
     Group 1: "stackoverflow.com"
     Group 2: "/questions/tagged/regex"

參考: https://stackoverflow.com/questions/3512471/what-is-a-non-capturing-group-what-does-do

(?=pattern) (?!pattern) (?<=pattern) (?<!pattern) 預查匹配
  • (?=pattern) 正向確定預查, 判斷當前匹配後面是否有 pattern 所述字符串.
  • (?!pattern) 正向否認預查, 判斷當前匹配後面是否不包含 pattern 所述字符串.
  • (?<=pattern) 反向確定預查, 判斷當前匹配前面是否有 pattern 所述字符串.
  • (?<!pattern) 反向否認預查, 判斷當前匹配前面是否不包含 pattern 所述字符串.

預查不消耗字符,也就是說,在一個匹配發生後,在最後一次匹配以後當即開始下一次匹配的搜索,而不是從包含預查的字符以後開始。

參考:

bar(?=bar)     finds the 1st bar ("bar" which has "bar" after it)
bar(?!bar)     finds the 2nd bar ("bar" which does not have "bar" after it)
(?<=foo)bar    finds the 1st bar ("bar" which has "foo" before it)
(?<!foo)bar    finds the 2nd bar ("bar" which does not have "foo" before it)

https://stackoverflow.com/questions/2973436/regex-lookahead-lookbehind-and-atomic-groups

使用經驗

  • 儘可能少些長正則, 由於難以維護
  • 在寫稍長的正則表達式時, 能夠分段寫, 寫一段測一段
  • 若是正則很是複雜, 並且麻煩, 就要考慮是不是正則適合的場景, 須要考慮使用其餘方式來實現.

特殊示例

System.out.println("www".replaceAll("a?", "替換"));

// 結果是: 替換w替換w替換w替換

參考

https://docs.oracle.com/javase/9/docs/api/java/util/regex/Pattern.html#sum

https://en.wikipedia.org/wiki/Comparison_of_regular_expression_engines

https://www.cnblogs.com/exmyth/p/7774918.html

https://blog.csdn.net/qq_19865749/article/details/77478489

相關文章
相關標籤/搜索