正則表達式(代碼java版)


重新發布於2020年09月27日,寫於2016年

看了好些天的正則表達式,終於有時間來寫一篇關於它的博客了。也是因為前段時間做標簽處理的工作用到,用正則匹配標簽規則,少寫了不少代碼。在有的地方使用正則表達式確實特別棒。參考博文http://blog.csdn.net/yaerfeng/article/details/28855587 ,文中提到程序員的七種基本技能,確實各種語言,系統里幾乎都有對正則的支持,雖說不用精通,但也要基本運用沒問題。

元字符

元字符標識在正則表達式中有特殊含義的字符,正是由它們,正則表達式才真正存在。JAVA中支持的元字符列表有:([{\^-$|}])?*+.

  • ( ):正則組中使用
  • [ ]:字符類中表示一個字符
  • { }:數量范圍標識
  • \:預定義字符類中使用
  • ^ $:邊界標識
  • -:字符類中表示某個范圍時使用,和"[]"配合使用
  • |:邏輯或
  • ? * +:預定義數量詞
  • ^:邏輯非
  • .:點號匹配除換行符的任意字符 (這個地方任然有點疑問)

這里要特別說明一個符號&,雖然&&字符類中扮演着邏輯與運算,但卻不在元字符行列中

檢測工具

為了學習簡單,寫了一段測試代碼做檢測用,當然你也可以使用網上的檢測工具,由於目前各個語言正則的引擎各有取舍,所以在線工具的檢測結果不一定和代碼檢測結果相同(基本上沒太大出入),但用於理解正則,還是很有用的。

簡單案例:匹配5個連續的數字
正則表達式為[0-9]{5},先用開源中國的在線測試工具測試一下。待匹配的字符串為“自由12345飛翔

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("自[]由飛翔", "\\["); 
//執行結果如下
[  

如上,通過轉義,匹配到 “自[]由飛翔”中的元字符 [
而在線工具可以直接將字符讀入,所以不用\\\[就表示匹配字符[,如下

案例3,普通字符&

simpletest("自&&由飛翔", "&&"); 
//執行結果如下
&&
simpletest("自&由&飛翔", "&");
//執行結果如下
&
&  

案例3可以看出,雖然&&有特殊含義,但單獨用時,不用轉義,和普通字符完全相同

字符類

字符類(character class),這里這個詞語是個專用名詞,在JAVA API 中的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 中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 中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 中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一模一樣的內容。


免責聲明!

本站轉載的文章為個人學習借鑒使用,本站對版權不負任何法律責任。如果侵犯了您的隱私權益,請聯系本站郵箱yoyou2525@163.com刪除。



 
粵ICP備18138465號   © 2018-2025 CODEPRJ.COM