正則表達式的總結

正則表達式學習

  • 正則表達式就用來是解決字符串搜索與替換問題的,能夠用正則表達式引擎判斷一個字符串是否是符合正則表達式引擎中的定義好的規則,而且規則每每是幾個簡單地字符表示的,看上去很簡潔,只不過若是不會正則表達式的,看起來會更加難懂
  • 正則表達式的做用核心是使用狀態機的思想進行模式匹配
  • 我的理解寫好正則表達式只須要組合好 惟一匹配+重複匹配+規則關係便可

字符

  • 精確匹配,也就是如出一轍java

    System.out.println("t".matches("t"));  // true
    System.out.println("t*".matches("t\\*"));  // true 注意對於通常字符串 *不是特殊字符 在正則中*是特殊字符,所以要表達其原本的意思須要使用轉義(此轉義時正則中的轉義,不是通常的轉義字符),即\*,可是\*並不是標準的轉義字符,所以使用\字符與*字符拼接標識轉義(此轉義時正則中的轉義,而不是通常字符串中的轉義),而表達字符則須要\\
    System.out.println("$".matches("\\$"));  // true
    
    System.out.println("\t".matches("\t"));  // false 正則匹配轉義字符,源字符串解析不解析爲轉義
    System.out.println("\t".matches("\\t")); // true正則從左到右匹配,在正則中用到了對於\的轉義標識\字符串自己,這裏匹配時把普通字符串中的轉義字符視爲兩個字符,至關於精確匹配
    System.out.println("\\t".matches("\\t")); // false 
    System.out.println("\\t".matches("\\\\t")); // true 正則表達式中匹配轉義,源字符串不匹配轉義
    複製代碼
    • 對於正常的字符串來講,除了普通字符和轉義字符以外,特殊字符有以下幾個git

      • \ 做爲轉義字符的標識符,若是想不作爲標識符使用,而是單獨做爲字符串自己使用,必須使用\\github

      • ' 單引號,Java的字符,js的字符串,均可以使用單引號標識,所以爲了不混淆使用\'標識單引號自己正則表達式

      • "雙引號,多種語言的字符都使用雙引號標識,爲了不混淆使用\"標識雙引號字符自己markdown

    • 正則接受通常的轉義字符,也接受正則本身引入的所謂的轉義字符(提供正則功能)oop

      • 對於正則引擎來講對於通常的轉義字符,\與後邊的被轉譯字符視爲一個字符(事實上也確實如此),而對於正則本身引入的轉義字符,\與後邊的被轉譯字符視爲兩個字符,只不過這種字符組合構成了轉義的功能
        • 可是對於待匹配或替換處理的原始字符串不會把轉義字符當作一個字符來處理,而是拆開處理
      • 正則本身引入的字符
        • 讓特殊字符失去其本意的轉義 System.out.println("t*".matches("t\\*"));
        • 提供特殊功能的轉義 \w \d
  • 使用正則表達式引擎提供的特殊字符時務必注意不要與轉義字符混淆post

    • 轉義字符的做用是讓一個特殊字符失去其自己的含義,而做爲一個普通的字符,對於普通字符來講即便使其具備特殊的含義性能

      • 特殊字符以及其自己含有的特殊含義包括:學習

        • 表示惟一匹配(匹配有限數量的字符)網站

          • . 匹配一個而且必須是1個的除了換行符以外的任意字符

          • \d 匹配一個數字

          • \D除了數字以外的字符

          • \w字母、數字、下劃線

          • \W除了字母、數字、下劃線以外的字符

          • \s空白字符(空格+製表符+換頁符+換行符)

          • \S除了空白字符以外的其餘字符

          • 指定範圍的匹配

            • [123] 枚舉匹配
              • 枚舉匹配時須要注意,方括號內能夠直接枚舉特殊字符,包括 ?+ 等等而不須要作轉義便可表示字符串自己*
            • [n-m1-2A-b]指定範圍匹配(範圍的起始點與終止點都包含)(注意多範圍之間無空格)
              • 使用^取非 注意僅在範圍匹配,也就是方括號內部使用^作取非
          • 除了上述的特殊字符外,正則還提供了一些其餘的特殊字符,部分特殊字符與普通的轉義字符重複

            image-20210213152433563
        • 重複匹配(匹配無限數量的字符)(通配符+集合區間)(重複匹配做爲修飾符放在惟一匹配的字符後邊)

          • 星號 匹配任意多個符合規則的字符
          • 加號 匹配至少一個
          • ? 匹配0個或者1個
          • {n} 匹配n個
          • {n,m}匹配至少n個,至多m個,m省略表示至少含義,n設置爲0表示至多含義
      • 對於普通字符的轉義

        • image-20210212234927819
      • 正則表達式中須要進行轉義以表示字符自己的特殊字符

      • .

      • ?

      • $

      • ^

      • 方括號

      • 圓括號

      • 大括號

      • |

      • \

規則

位置邊界+正則模式

  • 位置邊界用來限制匹配的位置,經過限制匹配的位置篩選掉不符合條件的匹配

單詞邊界

  • \b字符邊界(注意這個正則的特殊字符與普通轉義字符中有重複,在正則中顯然做爲字符邊界處理)

    • b是boundary的首字母。在正則引擎裏它匹配的是能構成單詞的字符(\w)和不能構成單詞的字符(\W)中間的那個位置
  • \B標識非字符邊界

  • 案例

    String p = "The cat scattered his food all over the room.";
    System.out.println(p.replaceAll("\\bcat\\b", "*"));  // The * scattered his food all over the room.
    複製代碼
    • 注意正則的內置特殊字符的使用方式
    • 注意replaceAll API的使用:直接返回更改後的新的String,而不是把新的String賦給原字符串變量引用變量

字符串邊界

  • 適用於多行匹配的模式下對於字符串的匹配

    • ^標識字符串的開頭

      • 是否使用^邊界符號的區別在於,是不是針對整個字符串的匹配

        "(T|t)he" => The car is parked in the garage.    // 字符串中的兩個the都會被選中
        "^(T|t)he" => The car is parked in the garage.   // 只有字符串首部的The被選中,由於篩選的是以The或者the開頭的字符串
        複製代碼
    • $標識字符串的結尾

      • 一樣的,是否使用$邊界符號的區別在於,是不是針對整個字符串的匹配

        "(at\.)" => The fat cat. sat. on the mat.      // cat. sat. mat.都被選中
        "(at\.)$" => The fat cat. sat. on the mat.     // 只有最後一個mat.被選中
        複製代碼
  • 字符串邊界符號與正則模式的配合使用

    • 在多行字符串的匹配中當只是用字符串邊界符號時,僅僅能匹配第一行或最後一行,由於有換行符的存在因此只能匹配一行

      String p1 = String.join("\n", "I am scq000.", "I am scq000.");
      // *
      // I am scq000.
      System.out.println(p1.replaceAll("^I am scq000\\.", "*"));
      
      
      // I am scq000.
      // *
      System.out.println(p1.replaceAll("I am scq000\\.$", "*"));
      
      // 多行沒法匹配
      // I am scq000.
      // I am scq000.
      System.out.println(p1.replaceAll("^I am scq000\\.$", "*"));
      複製代碼
    • 多行匹配的引入

      System.out.println(p1.replaceAll("(?m)^I am scq000\\.$", "*"));
      複製代碼
      • 使用(?m)引入多行模式
      Pattern p = Pattern.compile("^I am scq000\\.$", Pattern.MULTILINE);
      Matcher m = p.matcher(p1);
      System.out.println(m.find());
      複製代碼
      • 使用Pattern解析正則,引入正則模式
      Pattern p1 = Pattern.compile("^.*b.*$");
      //輸出false,由於正則表達式中出現了^或$,默認只會匹配第一行,第二行的b匹配不到。
      System.out.println(p1.matcher("a\nb").find());
      Pattern p2 = Pattern.compile("^.*b.*$",Pattern.MULTILINE);
      //輸出true,指定了Pattern.MULTILINE模式,就能夠匹配多行了。
      System.out.println(p2.matcher("a\nb").find());
      複製代碼
      "/.at(.)?$/" => The fat
                      cat sat
                      on the mat.    只有mat匹配
        
      "/.at(.)?$/gm" => The fat
                      cat sat
                      on the mat.   fat sat mat都匹配
      複製代碼

正則模式

  • m 多行模式

  • g 全局模式 匹配所有的符合規則的而不僅是第一個

  • i忽略大小寫

  • 指定模式的兩種方式:

    • 在正則表達式中指定

      • 注意Java中使用正則表達式模式的方式
      String p1 = String.join("\n", "I Am scq000.", "I am scq000.");
      // 結果爲:
      // I Am scq000.
      // * 
      // 可見第一行並未匹配
      System.out.println(p1.replaceAll("I am scq000\\.", "*"));
      
      // 多行模式 大小寫無關模式
      // 結果爲:
      // *
      // * 
      // 可見所有都匹配
      System.out.println(p1.replaceAll("(?mi)I am scq000\\.", "*"));
      複製代碼
      • 在最開頭使用(?m) (?i)來標識正則模式,也可使用組合模式(?mi)
      • 全局模式不能使用此方法指定,全局仍是非全局更多的是經過API的方式,好比replaceAll replaceFirst
    • 在API參數中指定

      Pattern p1 = Pattern.compile("^.*b.*$");
      //輸出false,由於正則表達式中出現了^或$,默認只會匹配第一行,第二行的b匹配不到。
      System.out.println(p1.matcher("a\nb").find());
      Pattern p2 = Pattern.compile("^.*b.*$",Pattern.MULTILINE);
      //輸出true,指定了Pattern.MULTILINE模式,就能夠匹配多行了。
      System.out.println(p2.matcher("a\nb").find());
      複製代碼
      • Pattern解析正則時除了提供了多行模式外,還提供瞭如下幾種模式

        • DOTALL模式 用來解決正則表達式中的.通配符不包含換行符帶來的問題

          Pattern p1 = Pattern.compile("a.*b");
          //輸出false,默認點(.)沒有匹配換行符
          System.out.println(p1.matcher("a\nb").find());
          Pattern p2 = Pattern.compile("a.*b", Pattern.DOTALL);
          //輸出true,指定Pattern.DOTALL模式,能夠匹配換行符。
          System.out.println(p2.matcher("a\nb").find());
          複製代碼
        • UNIX_LINES

        • CASE_INSENSITIVE

        • LITERAL

        • UNICODE_CASE

        • CANON_EQ

        • UNICODE_CHARACTER_CLASS

        • 同時使用多個模式的案例

          Pattern p1 = Pattern.compile("^a.*b$");
          //輸出false
          System.out.println(p1.matcher("cc\na\nb").find());
          Pattern p2 = Pattern.compile("^a.*b$", Pattern.DOTALL);
          //輸出false,由於有^或&沒有匹配到下一行
          System.out.println(p2.matcher("cc\na\nb").find());
          Pattern p3 = Pattern.compile("^a.*b$", Pattern.MULTILINE);
          //輸出false,匹配到下一行,但.沒有匹配換行符
          System.out.println(p3.matcher("cc\na\nb").find());
          //指定多個模式,中間用|隔開
          Pattern p4 = Pattern.compile("^a.*b$", Pattern.DOTALL|Pattern.MULTILINE);
          //輸出true
          System.out.println(p4.matcher("cc\na\nb").find());
          複製代碼
      • 以上各個模式的用途可查看源碼註釋,每個模式都支持對應的正則表達式內嵌的標識方法,能夠參考其註釋

子表達式

  • 使用小括號將表達式進行拆分,獲得子正則表達的組合,更加靈活。
  • 一個簡單的例子:座機號碼的區號-電話號的組合,匹配以後,想拆分出區號與電話號,可使用split,substring等複雜的方法,可是比較麻煩,而且沒有複用性,使用分組能夠方便的進行拆分並獲得其匹配的值
  • 要想高效使用分組子表達式須要用到回溯引用
回溯引用
  • 回溯引用是在分組的基礎之上使用的,指的是模式的後邊的部分引用前邊部分的已經匹配了的子表達式,使用回溯表達式有如下兩點好處

    • 能夠寫出更加精簡高效的正則

      • 在正則表達式中直接使用回溯的語法是:
        • 回溯引用的語法像\1,\2,....,其中\1表示引用的第一個子表達式,\2表示引用的第二個子表達式,以此類推。而\0則表示整個表達式
        • 案例:匹配字符串中的兩個連續的相同的單詞 Hello what what is the first thing, and I am am scq000.---\b(\w+)\s\1
    • 可使用正則抽取分組信息

      • Pattern類配合Matchr類

        String regex20 = "([0-9]{3,4})-([1-9]{7,8})";
        Pattern pattern = Pattern.compile(regex20);
        Matcher matcher = pattern.matcher("0312-2686815");
        
        // 注意只有通過matches判斷後的matcher才能進行分組提取,不然會報錯No Match Fund
        if (matcher.matches()) {
          // 注意分組從1開始,序號爲0的分組是字符串總體
          // 區號
          System.out.println(matcher.group(1));
          // 電話號
          System.out.println(matcher.group(2));
          // 匹配的總體
          System.out.println(matcher.group(0));
        } else {
          System.out.println("不匹配");
        }
        
        
        // 去掉區號
        System.out.println("0312-2686815".replaceAll(regex20, "$2"));
        複製代碼
        • "str.matches"方法內部使用的也是Pattern與Matchr,每一次調用方法都建立一個新的Patterm對象和一個新的Matcher對象,推薦直接定義一個Pattern來複用,以提高性能
        • 使用回溯進行分組提取時,使用的特殊字符爲$1而不是\1,一樣注意$0表明總體,$1纔是第一個分組,以此類推
    • 若是要拒絕子正則表達式被引用,則在子正則的前邊加上?:

      String regex20 = "(?:[0-9]{3,4})-([1-9]{7,8})";
      // $0仍然是表示總體,$1由第二個子表達式補位,此時再引用$2 會報錯No Group 2
      System.out.println("0312-2686815".replaceAll(regex20, "$2"));
      複製代碼
    • 由上邊的非捕獲正則?:引出前向查找和後向查找(也能夠稱做零寬度斷言)----相對來講說比較難以理解

      • 使用斷言的說法更容易理解,由於其做用就在於對子表達式附加斷言,從而爲正則匹配添加篩選條件,所謂先後就是表徵這個斷言限制條件是做用在子表達式前仍是後(關於後發的寫法的記憶手段:從後指向前的箭頭),所謂正負表徵的就是邏輯上的是與否(正負在寫法上的差異就是=與!的差異)
      • 須要注意的是斷言表達式自己不做爲匹配的內容,只是做爲斷言的輔助說明
      • 正先行斷言 (?=regex)
        • "(T|t)he(?=\sfat)" => The fat cat sat on the mat. 匹配第一個The
        • 除斷言以外的表達式能夠匹配兩個the,加入斷言後只匹配第一個The,由於其後邊是 fat
      • 負先行斷言 (?!regex)
        • "(T|t)he(?!\sfat)" => The fat cat sat on the mat. 匹配第二個the
        • 篩選出不知足斷言的
      • 正後發斷言 (?<=regex)
        • "(?<=(T|t)he\s)(fat|mat)" => The fat cat sat on the mat. 匹配fat和mat
        • 斷言條件的位置在子正則以前
      • 負後發斷言 (?<!regex)
        • "(?<!(T|t)he\s)(cat)" => The cat sat on cat. 匹配第二個cat

邏輯組合

  • 在正則中,多個規則寫在一塊兒時,默認是與關係
  • |或關係
  • [^] 枚舉或指定範圍的匹配的取反
  • ! 負先發斷言,負後發斷言

貪婪匹配與惰性匹配

  • 默認是貪婪匹配,貪婪匹配會在從左到右的匹配中匹配儘量多的字符 "/(.*at)/" => The fat cat sat on the mat. 整個字符串所有匹配
  • 貪婪匹配更改成惰性匹配使用?便可:將?加在重複匹配的修飾符(* + ?等)後邊便可
  • 案例
    • "/(.*?at)/" => The fat cat sat on the mat. at前邊是任意數量的字符,惰性匹配就在這個任意數量的修飾符後加上?標識儘量少匹配字符 所以只匹配The fat
    • 判斷字符串數字末尾的0的個數
      • 123000 -> "(\\d+)(0*)" -> "123000" "" 至少一個數字?我全都要
      • 123000 -> "(\\d+?)(0*)" -> "123" "000" 至少一個數字?那就給你1個吧!可是考慮到後邊的只想要0,那就把0前邊的都給你
      • 0000 -> "(\\d+?)(0*)" -> "0" "000" 至少一個數字,那就給你1個,其他的後邊的正好都要!哈哈
      • 9999 -> "(\\d??)(9*)" -> "" "9999" 無關緊要,正好後邊都想要呢,那你就沒了~

參考

相關文章
相關標籤/搜索