一、JDK提供的正則表達式

Java's java.util.regex 包包括:Pattern類、Matcher類、MatchResult接口和PatternSyntaxException異常:

  • Pattern 對象代表了編譯了的正則表達式(A compiled representation of a regular expression.)。
  • Matcher ,An engine that performs match operations on a character sequence by interpreting a Pattern.
  • MatchResult接口:The result of a match operation.
  • PatternSyntaxException對象,描述非法的regex patterns,Unchecked exception thrown to indicate a syntax error in a regular-expression pattern.

 同時還需要了解是CharSequence,JDK 1.4定義的新的接口,它提供了String和StringBuffer這兩個類的字符序列的抽象

interface CharSequence {

charAt(int i);

length();

subSequence(int start, int end);

toString();

}

為了實現這個新的CharSequence接口,String,StringBuffer以及CharBuffer都作了修改,很多的正則表達式的操作都要拿CharSequence作參數。

 

Matcher類的幾個重要的方法:

  • boolean matches():Pattern匹配整個字符串
  • boolean lookingAt():Pattern匹配字符串的開頭
  • boolean find():發現CharSequence里的,與pattern相匹配的多個字符序列
    boolean find(int start):告訴方法從參數start位置開始查找

group的概念

Group是指里用括號括起來的,能被后面的表達式調用的正則表達式。

Group 0 表示整個表達式,group 1表示第一個被括起來的group,以此類推。所以;

A(B(C))D

里面有三個group:group 0是ABCD, group 1是BC,group 2是C。

你可以用下述Matcher方法來使用group:

  • public int groupCount( )返回matcher對象中的group的數目。不包括group0。
  • public String group( ) 返回上次匹配操作(比方說find( ))的group 0(整個匹配)
  • public String group(int i)返回上次匹配操作的某個group。如果匹配成功,但是沒能找到group,則返回null。
  • public int start(int group)返回上次匹配所找到的group的開始位置。
  • public int end(int group)返回上次匹配所找到的group的結束位置,最后一個字符的下標加一。
  • split( ) 是指將以正則表達式為界,將字符串分割成String數組,如果帶有limit參數,split( )會限定分割的次數。
  • replaceFirst(String replacement)將字符串里,第一個與模式相匹配的子串替換成replacement。
  • replaceAll(String replacement),將輸入字符串里所有與模式相匹配的子串全部替換成replacement。
  • appendReplacement(StringBuffer sbuf, String replacement)對sbuf進行逐次替換,而不是像replaceFirst( )或replaceAll( )那樣,只替換第一個或全部子串。這是個非常重要的方法,因為replacement(replaceFirst( )和replaceAll( )只允許用固定的字符串來充當replacement)。有了這個方法,你就可以編程區分group,從而實現更強大的替換功能。
  • 調用完appendReplacement( )之后,為了把剩余的字符串拷貝回去,必須調用appendTail(StringBuffer sbuf, String replacement)。

 

使用group可以做到逐步縮小匹配的范圍,直至精確匹配的目的。

start( )和end( ):如果匹配成功,start( )會返回此次匹配的開始位置,end( )會返回此次匹配的結束位置,即最后一個字符的下標加一。如果之前的匹配不成功(或者沒匹配),那么無論是調用start( )還是end( ),都會引發一個IllegalStateException。

 

二、4大使用方法:

查詢

String str="abc efg ABC";

 

String regEx="a|f"; //表示a或f

 

Pattern p=Pattern.compile(regEx);

 

Matcher m=p.matcher(str);

 

boolean rs=m.find();

 

如果str中有regEx,那么rs為true,否則為flase。如果想在查找時忽略大小寫,則可以寫成Pattern p=Pattern.compile(regEx,Pattern.CASE_INSENSITIVE);

 

 

提取

String regEx=".+\\\\(.+)$";

 

String str="c:\\dir1\\dir2\\name.txt";

 

Pattern p=Pattern.compile(regEx);

 

Matcher m=p.matcher(str);

 

boolean rs=m.find();

 

for(int i=1;i<=m.groupCount();i++){

 

System.out.println(m.group(i));

 

}

 

以上的執行結果為name.txt,提取的字符串儲存在m.group(i)中,其中i最大值為m.groupCount();

 

 

分割

 

String regEx="::";

 

Pattern p=Pattern.compile(regEx);

 

String[] r=p.split("xd::abc::cde");

 

執行后,r就是{"xd","abc","cde"},其實分割時還有跟簡單的方法:

 

String str="xd::abc::cde";

 

String[] r=str.split("::");

 

 

替換或者刪除

String regEx="a+"; //表示一個或多個a

 

Pattern p=Pattern.compile(regEx);

 

Matcher m=p.matcher("aaabbced a ccdeaa");

 

String s=m.replaceAll("A");

 

結果為"Abbced A ccdeA"

 

如果寫成空串,既可達到刪除的功能,比如:

 

String s=m.replaceAll("");

 

結果為"bbced ccde"

 

 

三、一個實際的例子

下面的函數是一個實際應用的例子,需求是需要將抓取的網頁中的<img src=''http://aa.bb.cc.com/images/**.jpg"> 中的地址,保存到一個地址列表中(以供抓取圖片使用),并將絕對地址替換成本地的相對地址,即<img src="images/**.jpg">。

public static Map<String, String> getImagesURL(String description) {

      Map<String, String> map = new HashMap<String, String>();
        // img 的正則表達式:匹配<img>標簽       
        String imgPattern = "<\\s*img\\s+([^>]+)\\s*>";
        Pattern pattern1 = Pattern.compile(imgPattern, Pattern.CASE_INSENSITIVE);
        Matcher matcher = pattern1.matcher(description);

        // img src元素的正則表達式:匹配img標簽內的src屬性
        String srcPattern = "\\s*src\\s*=\\s*\"([^\"]+)\\s*\"";
        Pattern pattern2 = Pattern.compile(srcPattern, Pattern.CASE_INSENSITIVE);

        while (matcher.find()) {

           //group()返回符合表達式的內容
            Matcher matcher2 = pattern2 .matcher(matcher.group());
            // 一定要find(),這是實際的匹配動作
            if (matcher2.find()) {
                String src = matcher2.group();
                log.info(src);
                int i2 = src.lastIndexOf('/');
                int i1 = src.indexOf("http");
                if (i1 != -1) {
                    map.put(src.substring(i2 + 1, src.length() - 1), src
                            .substring(i1, src.length() - 1));
                }
            }
        }
        log.debug("圖片:" + map);
        return map;
    }

 

整體思路是先匹配到img標簽,然后匹配src屬性,并修改src的屬性

"<\\s*img\\s+([^>]+)\\s*>" 的解釋:

\\s* :0 或多個空格   \\s+: 至少一個空格

([^>]+):指的是非>的所有的字符,至少一個

 

常用的正則表達式(參考jdk的regex文檔)

字符集類

.                            表示任意一個字符

[abc]                     表示字符a,b,c中的任意一個(與a|b|c相同)

[^abc]                   除a,b,c之外的任意一個字符(否定)

[a-zA-Z]                從a到z或A到Z當中的任意一個字符(范圍)

[abc[hij]]              a,b,c,h,i,j中的任意一個字符(與a|b|c|h|i|j相同)(并集)

[a-z&&[hij]]          h,i,j中的一個(交集)

\s                          空格字符(空格鍵, tab, 換行, 換頁, 回車)

\S                         非空格字符([^\s])

\d                         一個數字,也就是[0-9]

\D                         一個非數字的字符,也就是[^0-9]

\w                        一個單詞字符(word character),即[a-zA-Z_0-9]

\W                       一個非單詞的字符,[^\w]

字符類:

B                         字符B

\xhh                    16進制值0xhh所表示的字符

\uhhhh                16進制值0xhhhh所表示的Unicode字符

\t                         Tab

\n                        換行符

\r                         回車符

\f                        換頁符

\e                       Escape

邏輯運算符

XY                      X 后面跟著 Y

X|Y                    X或Y

(X)                     一個"要匹配的組(capturing group)". 以后可以用\i來表示第i個被匹配的組。

邊界匹配符

^                      一行的開始

$                      一行的結尾

\b                    一個單詞的邊界

\B                    一個非單詞的邊界

\G                   前一個匹配的結束

 

數量表示符

"數量表示符(quantifier)"的作用是定義模式應該匹配多少個字符。

  • Greedy(貪婪的): 除非另有表示,否則數量表示符都是greedy的。Greedy的表達式會一直匹配下去,直到匹配不下去為止。(如果你發現表達式匹配的結果與預期的不符),很有可能是因為,你以為表達式會只匹配前面幾個字符,而實際上它是greedy的,因此會一直匹配下去。
  • Reluctant(勉強的): 用問號表示,它會匹配最少的字符。也稱為lazy, minimal matching, non-greedy, 或ungreedy。
  • Possessive(占有的): 目前只有Java支持(其它語言都不支持)。它更加先進,所以你可能還不太會用。用正則表達式匹配字符串的時候會產生很多中間狀態,(一般的匹配引擎會保存這種中間狀態,)這樣匹配失敗的時候就能原路返回了。占有型的表達式不保存這種中間狀態,因此也就不會回頭重來了。它能防止正則表達式的失控,同時也能提高運行的效率。

 

Greedy                    Reluctant                           Possessive                      匹配

 

X?                            X??                                      X?+                                  匹配一個或零個X

X*                            X*?                                      X*+                                  匹配零或多個X

X+                           X+?                                      X++                                 匹配一個或多個X

X{n}                        X{n}?                                   X{n}+                               匹配正好n個X

X{n,}                       X{n,}?                                 X{n,}+                              匹配至少n個X

X{n,m}                   X{n,m}?                                X{n,m}+                           匹配至少n個,至多m個X

 

 

參考資料

要想進一步學習正則表達式,建議看Mastering Regular Expression, 2nd Edition,作者Jeffrey E. F. Friedl (O’Reilly, 2002)。

"Regular Expressions and the Java Programming Language," Dana Nourie and Mike McCloskey (Sun Microsystems, August 2002) presents a brief overview of java.util.regex, including five illustrative regex-based applications:
http://developer.java.sun.com/developer/technicalArticles/releases/1.4regex/

http://www.tkk7.com/beike/archive/2006/04/28/43832.html 

http://wcjok.bokee.com/4293762.html