1. 程式人生 > >正則表示式(程式碼java版)

正則表示式(程式碼java版)

[toc] >重新發佈於2020年09月27日,寫於2016年 看了好些天的正則表示式,終於有時間來寫一篇關於它的部落格了。也是因為前段時間做標籤處理的工作用到,用正則匹配標籤規則,少寫了不少程式碼。在有的地方使用正則表示式確實特別棒。參考[博文http://blog.csdn.net/yaerfeng/article/details/28855587](http://blog.csdn.net/yaerfeng/article/details/28855587) ,文中提到程式設計師的七種基本技能,確實各種語言,系統裡幾乎都有對正則的支援,雖說不用精通,但也要基本運用沒問題。 ## 元字元 元字元標識在正則表示式中有特殊含義的字元,正是由它們,正則表示式才真正存在。JAVA中支援的元字元列表有:`([{\^-$|}])?*+.` - `(` `)`:正則組中使用 - `[` `]`:字元類中表示一個字元 - `{` `}`:數量範圍標識 - `\`:預定義字元類中使用 - `^` `$`:邊界標識 - `-`:字元類中表示某個範圍時使用,和"[]"配合使用 - `|`:邏輯或 - `?` `*` `+`:預定義數量詞 - `^`:邏輯非 - `.`:點號匹配除換行符的任意字元 (這個地方任然有點疑問) **這裡要特別說明一個符號`&`,雖然`&&`在`字元類`中扮演著`邏輯與`運算,但卻不在元字元行列中** ## 檢測工具 為了學習簡單,寫了一段測試程式碼做檢測用,當然你也可以使用網上的檢測工具,由於目前各個語言正則的引擎各有取捨,所以線上工具的檢測結果不一定和程式碼檢測結果相同(基本上沒太大出入),但用於理解正則,還是很有用的。 **簡單案例:匹配5個連續的數字** 正則表示式為`[0-9]{5}`,先用開源中國的線上[測試工具](http://tool.oschina.net/regex/)測試一下。待匹配的字串為“`自由12345飛翔`” ![](https://img2020.cnblogs.com/blog/587830/202009/587830-20200927095908530-1300270259.png) JAVA檢測程式碼如下 ``` /** * 檢測簡單方法 * @param target //待查詢匹配的字串 * @param regex //匹配規則的正則表示式 */ public static void simpletest(String target,String regex) { Pattern p = Pattern.compile(regex);//java.util.regex.Pattern; Matcher matcher = p.matcher(target);//java.util.regex.Pattern; while (matcher.find()) { System.out.println(matcher.group(0)); } } ``` ``` simpletest("自由12345飛翔", "[0-9]{5}"); //執行結果如下 12345 ``` ## 普通字元 所謂普通字元即為非元字元,上文中提到的元字元列表,即不是上面列表中的字元,就視為普通字元,普通字元為原樣匹配 **案例1,普通字元** ``` simpletest("自123由飛12333翔", "123"); //執行結果如下 123 123 ``` 如上案例中,會去目標字串"`自123由飛12333翔`"中查詢`123`,由於123為普通字元,沒有特殊含義,此時原樣匹配,所以匹配到"`自123由飛12333翔`"中兩組`123` **案例2,元字元** ``` simpletest("自[]由飛翔", "["); //執行結果如下 直接報錯 Exception in thread "main" java.util.regex.PatternSyntaxException: Unclosed character class ``` 如果要讓元字元原樣匹配,則需要用`\`轉義元字元,JAVA中`\\`才表示普通字串的`\`,所以為`\\[` ``` simpletest("自[]由飛翔", "\\["); //執行結果如下 [ ``` 如上,通過轉義,匹配到 “自`[`]由飛翔”中的元字元 `[` 而線上工具可以直接將字元讀入,所以不用`\\`,`\[`就表示匹配字元`[`,如下 ![](https://img2020.cnblogs.com/blog/587830/202009/587830-20200927100244572-199023290.png) **案例3,普通字元&** ``` simpletest("自&&由飛翔", "&&"); //執行結果如下 && simpletest("自&由&飛翔", "&"); //執行結果如下 & & ``` 案例3可以看出,雖然`&&`有特殊含義,但單獨用時,不用轉義,和`普通字元`完全相同 ## 字元類 `字元類(character class)`,這裡這個詞語是個專用名詞,在[JAVA API](http://tool.oschina.net/apidocs/apidoc?api=jdk-zh) 中的Pattern類中我們可以看到字元類的一個列表。**一個`[]`中的規則叫一個字元類,一個字元類僅匹配一個字元(一個位置)** - [abc] a、b 或 c(簡單類) - [^abc] 任何字元,除了 a、b 或 c(否定) - [a-zA-Z] a 到 z 或 A 到 Z,兩頭的字母包括在內(範圍) **數字範圍也能類似表示如[0-9]代表0到9中任意一個數字** - [a-d[m-p]] a 到 d 或 m 到 p:[a-dm-p](並集) **等同於[a-d|[m-p]] 等同與 [[a-d]|[m-p]] ** - [a-z&&[def]] d、e 或 f(交集) - [a-z&&[^bc]] a 到 z,除了 b 和 c:[ad-z](減去) **差集** - [a-z&&[^m-p]] a 到 z,而非 m 到 p:[a-lq-z](減去)**差集** **案例** ``` simpletest("abcd", "[abc]"); //執行結果如下 a b c simpletest("abcd", "[^abc]"); //執行結果如下 d simpletest("abcd", "[a-zA-Z]"); //執行結果如下 a b c simpletest("an", "[a-d[m-p]]"); //執行結果如下 a n simpletest("abcd", "[a-z&&[def]]"); //執行結果如下 d simpletest("abcd", "[a-z&&[^bc]]"); //執行結果如下 a d simpletest("an", "[a-z&&[^m-p]]"); //執行結果如下 a ``` 現在我們清楚的看出來,一個字元類,也就是`[]`及中間內容代表一個範圍,表示匹配一個字元,中括號中包含這個字元可能出現的所有情況,由於檢測工具中使用了迴圈查詢匹配,所以輸出結果會查詢到多個字元打印出來 ## 預定義字元類 預定義字元類,是正則表示式中代表一組字元的特殊表示,由`\`打頭,如下為[JAVA API](http://tool.oschina.net/apidocs/apidoc?api=jdk-zh) 中Pattern類的預定義字元類列表 `.`: 任何字元(與行結束符可能匹配也可能不匹配) `\d`:數字:[0-9] `\D`:非數字: [^0-9] `\s`:空白字元:[ \t\n\x0B\f\r] `\S`:非空白字元:[^\s] `\w`:單詞字元:[a-zA-Z_0-9] `\W`:非單詞字元:[^\w] **案例** ``` simpletest("自由12345飛翔", "\\d{5}"); //同理,\d在JAVA中需要轉義 //執行結果如下 12345 simpletest("自由12345飛翔", "\\D+"); //預定義數量詞稍後再說 //執行結果如下 自由 飛翔 simpletest("自由 \t飛翔", "\\s+"); //執行結果如下 ` `//此處匹配到一個空格和一個製表符 simpletest("自由 \t飛翔", "\\S+"); //執行結果如下 自由 飛翔 simpletest("自由abc飛翔", "\\w+"); //執行結果如下 abc simpletest("自由abc飛翔", "\\W+"); //執行結果如下 自由 飛翔 ``` ## 數量詞 #### 預設數量詞 正則匹配中字元都有要匹配的數量,如果沒有加數量詞,預設數量為1,匹配一個的意思 **案例1** ``` simpletest("自由12345飛翔", "\\D"); //執行結果如下 自 由 飛 翔 simpletest("自由12345飛翔", "\\D+"); //執行結果如下 自由 飛翔 ``` 如上案例中`\\D`代表匹配查詢非數字字元,預設數量詞為1,所以查詢到一個非數字字元後直接列印後,便進入下次查詢,結果如上第一段程式碼 如上案例中`\\D+`中引入`+`號預定義量詞,代表匹配大於等於1次的連續非數字字元,所以匹配到自由後進入下一次查詢 #### 自定義量詞 使用者希望匹配幾次,就給定匹配次數,我這裡姑且叫它自定義量詞吧。由大括號,上下限數量組成,上限數量可以缺少 - `{n}`:恰好n個 - `{n,}`:大於等於n個 - `{n,m}`:大於等於n個,小於等於m個 **注意:並沒有{,m}這種寫法** **案例** ``` simpletest("自由12345飛翔", "\\d{2}"); //執行結果如下 12 simpletest("自由12345飛翔", "\\d{2,}"); //執行結果如下 12345 simpletest("自由12345飛翔", "\\d{2,4}"); //執行結果如下 1234 simpletest("自由12345飛翔", "\\d{,7}"); //執行結果如下 報錯 Exception in thread "main" java.util.regex.PatternSyntaxException: Illegal repetition near index 1 ``` 從上面案例中我們已經看出,`量詞只形容最近字元的數量`,大括號中可以指定具體字元的具體數量或者範圍。 #### 預定義量詞 預定義量詞是正則中用`?`,`+`,`*` 三個符號表示特定意思的量詞 - `?`:一次或者零次 - `+`:一次或者多次 - `*`:零次或者零次以上 **案例1** ``` simpletest("自由12345飛翔", "\\d?"); //執行結果如下 //空行 //空行 1 2 3 4 5 //空行 //空行 ``` 這裡要特別解釋一下兩個空行的產生,正則引擎去`自由12345飛翔`查詢`\\d?`時,逐個字元從左到右查詢,由於`?`表示一個或者零個,所以第一個字元匹配成功得到0個數字,也就是一個空字元,所以打印出來,而後面匹配到1個數字“1”打印出來,在匹配到1個數字“2”打印出來、、 **案例2** ``` simpletest("自由12345飛翔", "\\d+"); //執行結果如下 12345 ``` 這裡匹配至少一個數字,直接匹配到5個數字“12345”輸出 **案例3** ``` simpletest("自由12345飛翔", "\\d*"); //執行結果如下 //空行 //空行 12345 //空行 //空行 ``` 這裡出現空行的原因和`案例1`中相同,因為\\d*代表0次或者0次以上 ## 邊界識別符號 如下為[JAVA API](http://tool.oschina.net/apidocs/apidoc?api=jdk-zh) 中Pattern類的邊界識別符號列表 `^` :行的開頭 `$` :行的結尾 `\b` :單詞邊界 `\B` :非單詞邊界 `\A` :輸入的開頭 `\G` :上一個匹配的結尾 `\Z` :輸入的結尾,僅用於最後的結束符(如果有的話) `\z` :輸入的結尾 目前並沒完全弄明白所有邊界識別符號的用法,抱歉,僅演示幾個。 **案例1** ``` simpletest("自由12345飛翔", "^\\d+"); simpletest("12345飛翔", "^\\d+"); //執行結果如下 12345 ``` `^\\d+`查詢行開頭緊跟1次或多次數字,顯然`自由12345飛翔`匹配失敗,因為`12345`並非行首,而`12345飛翔`匹配成功得到`12345` **案例2** ``` simpletest("自由12345飛翔", "^\\d+$"); simpletest("12345飛翔", "^\\d+$"); simpletest("自由12345", "^\\d+$"); simpletest("12345", "^\\d+$"); //執行結果如下 12345 ``` 當`\\d+`前面加上行首邊界,後面加上行尾邊界後,該正則只能匹配到一串純數字,且數量滿足`+`量詞 **案列3** ``` simpletest("自由12345飛翔", "\\b\\d+"); //執行結果如下 //啥也沒有 simpletest("自由 12345飛翔", "\\b\\d+"); //執行結果如下 12345 simpletest("自由12345 飛翔", "\\d+\\b"); //執行結果如下 12345 simpletest("12345", "\\d+\\b"); //執行結果如下 12345 simpletest("12345", "\\b\\d+"); //執行結果如下 12345 ``` 從案例3中我們可以看出所謂的`\b`單詞邊界就是指空格或者行首行位(或許還有其他,反正匹配到一個連續的詞的結束或者開始位置) **案例4** ``` simpletest("自由12345飛翔", "\\B\\d+"); //執行結果如下 12345 simpletest("自由 12345飛翔", "\\B\\d+"); //執行結果如下 2345 ``` `\B`為非單詞邊界,和\b恰好相反,但`案例4`中效果卻和`案例3`中不是相反,不能匹配到`12345`,因為前面有空格,但`2345`前面是`1`,是非單詞邊界,所以匹配成功 ## 正則組 至此前面,基本上把正則表示式簡單運用講完,現在我們來引入一個詞`正則組`,正則組是用`()`把一組字元當做一個整體,可以通過方法將這個組匹配到的字元單獨取出,同樣可以通過下標引用之前匹配到的該組的字元 #### 簡單應用 **案例** ``` @Test public void grouptest( ){ String regex="\\d(\\d+)(\\D+)"; String target="520LiLing"; Pattern p = Pattern.compile(regex); Matcher matcher = p.matcher(target); while (matcher.find()) { System.out.println(matcher.group(0)); System.out.println(matcher.group(1)); System.out.println(matcher.group(2)); System.out.println(matcher.group(3)); } } //執行結果如下 520LiLing 20 LiLing Exception in thread "main" java.lang.IndexOutOfBoundsException: No group 3 ``` 從簡單案例中可以看出`()`中匹配到的數字可以用matcher.group(1)方法取出,1代表第一組,案例中第一組為(\\d+),第二組為(\\D+),第三組沒找到,報錯。而matcher.group(1)代表整個正則表示式匹配到的內容。 #### 複雜組序 [JAVA API](http://tool.oschina.net/apidocs/apidoc?api=jdk-zh) 中Pattern類中有關於正則組順序的介紹,當遇到複雜的正則組時,怎麼來確定組的序號。 `((A)(B(C)))`表示一個正則表示式,A,B,C分別代表隨意一個表示式 - group(1):`((A)(B(C)))` - group(2):`(A)` - group(3):`(B(C))` - group(4):`(C)` 從上面的列表說明不難總結出一個規律,將正則表示式從左到右讀過來,依次遇到`()`中的左括號`(`依次編號,先遇到哪一組的左括號先編號 **案例** ``` @Test public void grouptest( ){ String regex="((\\d)(\\d+(\\D+)))"; String target="520LiLing"; Pattern p = Pattern.compile(regex); Matcher matcher = p.matcher(target); while (matcher.find()) { System.out.println(matcher.group(1)); System.out.println(matcher.group(2)); System.out.println(matcher.group(3)); System.out.println(matcher.group(4)); } } //執行結果如下 520LiLing 5 20LiLing LiLing ``` 案例和上文說明完全一致,1組為`((\\d)(\\d+(\\D+)))`,2組為`(\\d)`,3組為`(\\d+(\\D+))`,4組為`(\\D+)` #### 捕獲組 前面已經講過關於正則組的編號,以及引用,但這樣的作用似乎還不夠強大。捕獲組,就是將之前的正則組通過`\組序號`捕獲,如`\1`(任然需要轉義`\\1`),再次利用。(解釋起來太費勁,看案例吧) ``` @Test public void grouptest( ){ String regex="(\\d+).+\\1"; String target="520Li320Ling"; Pattern p = Pattern.compile(regex); Matcher matcher = p.matcher(target); while (matcher.find()) { System.out.println(matcher.group(0)); System.out.println(matcher.group(1)); } } //執行結果如下 20Li320 20 ``` 上面案例中,正則表示式`(\\d+).+\\1` 的意思就是先查詢到數字標記為`組1`,然後跟著任意字元,跟著`\\1`表示捕獲和`組1`一模一樣的