Java正則表達式之Pattern類實例詳解

前言

這個系列的文章我們使用以下的順序進行講解:

  • Pattern 詳解;
  • Matcher 詳解;
  • 正則表達式語法詳解。

接下來先來介紹 Pattern 類。

在Java中,java.util.regex包定義瞭正則表達式使用到的相關類,其中最主要的兩個類為:Pattern、Matcher:

  • Pattern 編譯正則表達式後創建一個匹配模式;
  • Matcher 使用Pattern實例提供的正則表達式對目標字符串進行匹配,是真正影響搜索的對象。。

另加一個新的例外類,PatternSyntaxException,當遇到不合法的搜索模式時,會拋出例外。

Pattern 概述

聲明:public final class Pattern implements java.io.Serializable

Pattern 類有final修飾,可知他不能被子類繼承。

含義:模式類,正則表達式的編譯表示形式。

註意:此類的實例是不可變的,可供多個並發線程安全使用。

Pattern 匹配模式(Pattern flags)

compile( )方法有一個版本,它需要一個控制正則表達式的匹配行為的參數:

Pattern Pattern.compile(String regex, int flag)

flag 的取值范圍

字段 說明
Pattern.UNIX_LINES         unix行模式,大多數系統的行都是以\n結尾的,但是少數系統,比如Windows,卻是以\r\n組合來結尾的,啟用這個模式之後,將會隻以\n作為行結束符,這會影響到^、$和點號(點號匹配換行符)。
        通過嵌入式標志表達式 (?d) 也可以啟用 Unix 行模式。
Pattern.CASE_INSENSITIVE         默認情況下,大小寫不敏感的匹配隻適用於US-ASCII字符集。這個標志能讓表達式忽略大小寫進行匹配。要想對Unicode字符進行大小不明感的匹配,隻要將UNICODE_CASE與這個標志合起來就行瞭。
        通過嵌入式標志表達式(?i)也可以啟用不區分大小寫的匹配。
        指定此標志可能對性能產生一些影響。
Pattern.COMMENTS ⇢⇢⇢⇢⇢⇢⇢⇢⇢⇢⇢⇢⇢⇢⇢         這種模式下,匹配時會忽略(正則表達式裡的)空格字符(不是指表達式裡的”//s”,而是指表達式裡的空格,tab,回車之類)和註釋(從#開始,一直到這行結束)。
        通過嵌入式標志表達式(?x) 也可以啟用註釋模式。
Pattern.MULTILINE         默認情況下,輸入的字符串被看作是一行,即便是這一行中包好瞭換行符也被看作一行。當匹配“^”到“$”之間的內容的時候,整個輸入被看成一個一行。啟用多行模式之後,包含換行符的輸入將被自動轉換成多行,然後進行匹配。
        通過嵌入式標志表達式 (?m) 也可以啟用多行模式。
Pattern.LITERAL         啟用字面值解析模式。
        指定此標志後,指定模式的輸入字符串就會作為字面值字符序列來對待。輸入序列中的元字符或轉義序列不具有任何特殊意義。
        標志 CASE_INSENSITIVE 和 UNICODE_CASE 在與此標志一起使用時將對匹配產生影響。其他標志都變得多餘瞭。
        不存在可以啟用字面值解析的嵌入式標志字符。
Pattern.DOTALL         在這種模式中,表達式 .可以匹配任何字符,包括行結束符。默認情況下,此表達式不匹配行結束符。
        通過嵌入式標志表達式 (?s) 也可以啟用此種模式(s 是 “single-line” 模式的助記符,在 Perl 中也使用它)。
Pattern.UNICODE_CASE         在這個模式下,如果你還啟用瞭CASE_INSENSITIVE標志,那麼它會對Unicode字符進行大小寫不敏感的匹配。默認情況下,大小寫不明感的匹配隻適用於US-ASCII字符集。
        指定此標志可能對性能產生影響。
Pattern.CANON_EQ         當且僅當兩個字符的正規分解(canonical decomposition)都完全相同的情況下,才認定匹配。比如用瞭這個標志之後,表達式a/u030A會匹配?。默認情況下,不考慮規范相等性(canonical equivalence)。
        指定此標志可能對性能產生影響。

在這些標志裡面,Pattern.CASE_INSENSITIVE,Pattern.MULTILINE,以及Pattern.COMMENTS是最有用的(其中Pattern.COMMENTS還能幫我們把思路理清楚,並且/或者做文檔)。註意,你可以用在表達式裡插記號的方式來啟用絕大多數的模式。這些記號就在上面那張表的各個標志的下面。你希望模式從哪裡開始啟動,就在哪裡插記號。

可以用OR (|)運算符把這些標志配合使用。

代碼示例

多行模式:Pattern.MULTILINE 示例

我測試瞭一下,也就是說如果沒有 MULTILINE 標志的話, ^ 和 $ 隻能匹配輸入序列的開始和結束;否則,就可以匹配輸入序列內部的行結束符。測試代碼如下:

import java.util.regex.*;

/**
 * 多行模式
 */
public class ReFlags_MULTILINE {

    public static void main(String[] args) {

        // 註意裡面的換行符
        String str = "hello world\r\n" + "hello java\r\n" + "hello java";

        System.out.println("===========匹配字符串開頭(非多行模式)===========");
        Pattern p = Pattern.compile("^hello");
        Matcher m = p.matcher(str);
        while (m.find()) {
            System.out.println(m.group() + "   位置:[" + m.start() + "," + m.end() + "]");
        }

        System.out.println("===========匹配字符串開頭(多行模式)===========");
        p = Pattern.compile("^hello", Pattern.MULTILINE);
        m = p.matcher(str);
        while (m.find()) {
            System.out.println(m.group() + "   位置:[" + m.start() + "," + m.end() + "]");
        }

        System.out.println("===========匹配字符串結尾(非多行模式)===========");
        p = Pattern.compile("java$");
        m = p.matcher(str);
        while (m.find()) {
            System.out.println(m.group() + "   位置:[" + m.start() + "," + m.end() + "]");
        }

        System.out.println("===========匹配字符串結尾(多行模式)===========");
        p = Pattern.compile("java$", Pattern.MULTILINE);
        m = p.matcher(str);
        while (m.find()) {
            System.out.println(m.group() + "   位置:[" + m.start() + "," + m.end() + "]");
        }
    }
}

===========匹配字符串開頭(非多行模式)===========
hello   位置:[0,5]
===========匹配字符串開頭(多行模式)===========
hello   位置:[0,5]
hello   位置:[13,18]
hello   位置:[25,30]
===========匹配字符串結尾(非多行模式)===========
java   位置:[31,35]
===========匹配字符串結尾(多行模式)===========
java   位置:[19,23]
java   位置:[31,35]

忽略大小寫:Pattern.CASE_INSENSITIVE 示例

有的時候,需要進行忽略大小寫的匹配。該例子實現匹配攝氏溫度和華氏溫度,對於以C、c、F和f結尾的溫度值都能匹配。

import java.util.regex.Pattern;

public class ReFlags_CASE_INSENSITIVE {

    public static void main(String[] args) {


        System.out.println("===========API忽略大小寫===========");
        String moneyRegex = "[+-]?(\\d)+(.(\\d)*)?(\\s)*[CF]";
        Pattern p = Pattern.compile(moneyRegex,Pattern.CASE_INSENSITIVE);

        System.out.println("-3.33c   " + p.matcher("-3.33c").matches());
        System.out.println("-3.33C   " + p.matcher("-3.33C").matches());


        System.out.println("===========不忽略大小寫===========");
        moneyRegex = "[+-]?(\\d)+(.(\\d)*)?(\\s)*[CF]";
        p = Pattern.compile(moneyRegex);

        System.out.println("-3.33c   " + p.matcher("-3.33c").matches());
        System.out.println("-3.33C   " + p.matcher("-3.33C").matches());


        System.out.println("===========正則內部忽略大小寫===========");
        moneyRegex = "[+-]?(\\d)+(.(\\d)*)?(\\s)*(?i)[CF]";
        p = Pattern.compile(moneyRegex);

        System.out.println("-3.33c   " + p.matcher("-3.33c").matches());
        System.out.println("-3.33C   " + p.matcher("-3.33C").matches());


        System.out.println("===========內部不忽略大小寫===========");
        moneyRegex = "[+-]?(\\d)+(.(\\d)*)?(\\s)*[CF]";
        p = Pattern.compile(moneyRegex);

        System.out.println("-3.33c   " + p.matcher("-3.33c").matches());
        System.out.println("-3.33C   " + p.matcher("-3.33C").matches());
    }
}

===========API忽略大小寫===========
-3.33c   true
-3.33C   true
===========不忽略大小寫===========
-3.33c   false
-3.33C   true
===========正則內部忽略大小寫===========
-3.33c   true
-3.33C   true
===========內部不忽略大小寫===========
-3.33c   false
-3.33C   true

啟用註釋:Pattern.COMMENTS 示例

啟用註釋,開啟之後,正則表達式中的空格以及#號行將被忽略。

import java.util.regex.Pattern;

public class ReFlags_COMMENTS {

    public static void main(String[] args) {

        System.out.println("===========API啟用註釋===========");
        String comments = "    (\\d)+#this is comments.";
        Pattern p = Pattern.compile(comments, Pattern.COMMENTS);
        System.out.println("1234   " + p.matcher("1234").matches());

        System.out.println("===========不啟用註釋===========");
        comments = "    (\\d)+#this is comments.";
        p = Pattern.compile(comments);
        System.out.println("1234   " + p.matcher("1234").matches());

        System.out.println("===========正則啟用註釋===========");
        comments = "(?x)    (\\d)+#this is comments.";
        p = Pattern.compile(comments);
        System.out.println("1234   " + p.matcher("1234").matches());

        System.out.println("===========不啟用註釋===========");
        comments = "    (\\d)+#this is comments.";
        p = Pattern.compile(comments);
        System.out.println("1234   " + p.matcher("1234").matches());

    }
}

===========API啟用註釋===========
1234   true
===========不啟用註釋===========
1234   false
===========正則啟用註釋===========
1234   true
===========不啟用註釋===========
1234   false

可以看到,#號到行尾的註釋部分和前面的空白字符都被忽略瞭。正則表達式內置的啟用註釋為(?x)。

啟用 dotall 模式:Pattern.DOTALL 示例

啟用dotall模式,一般情況下,點號(.)匹配任意字符,但不匹配換行符,啟用這個模式之後,點號還能匹配換行符。

import java.util.regex.Pattern;

public class ReFlags_DOTALL {

    public static void main(String[] args) {

        System.out.println("===========API啟用DOTALL===========");
        String dotall = "<xml>(.)*</xml>";
        Pattern p = Pattern.compile(dotall, Pattern.DOTALL);
        System.out.println("<xml>\\r\\n</xml>   " + p.matcher("<xml>\r\n</xml>").matches());

        System.out.println("===========不啟用DOTALL===========");
        dotall = "<xml>(.)*</xml>";
        p = Pattern.compile(dotall);
        System.out.println("<xml>\\r\\n</xml>   " + p.matcher("<xml>\r\n</xml>").matches());

        System.out.println("===========正則啟用DOTALL===========");
        dotall = "(?s)<xml>(.)*</xml>";
        p = Pattern.compile(dotall);
        System.out.println("<xml>\\r\\n</xml>   " + p.matcher("<xml>\r\n</xml>").matches());

        System.out.println("===========不啟用DOTALL===========");
        dotall = "<xml>(.)*</xml>";
        p = Pattern.compile(dotall);
        System.out.println("<xml>\\r\\n</xml>   " + p.matcher("<xml>\r\n</xml>").matches());

    }
}

===========API啟用DOTALL===========
<xml>\r\n</xml>   true
===========不啟用DOTALL===========
<xml>\r\n</xml>   false
===========正則啟用DOTALL===========
<xml>\r\n</xml>   true
===========不啟用DOTALL===========
<xml>\r\n</xml>   false

平白字符模式 模式:Pattern.LITERAL 示例

啟用這個模式之後,所有元字符、轉義字符都被看成普通的字符,不再具有其他意義。

import java.util.regex.Pattern;

public class ReFlags_LITERAL {

    public static void main(String[] args) {

        System.out.println(Pattern.compile("\\d", Pattern.LITERAL).matcher("\\d").matches());// true
        System.out.println(Pattern.compile("\\d", Pattern.LITERAL).matcher("2").matches());// false

        System.out.println(Pattern.compile("(\\d)+", Pattern.LITERAL).matcher("1234").matches());// false
        System.out.println(Pattern.compile("(\\d)+").matcher("1234").matches());// true

        System.out.println(Pattern.compile("(\\d){2,3}", Pattern.LITERAL).matcher("(\\d){2,3}").matches());// true
    }
}

附:貪婪匹配與懶惰匹配

考慮這個表達式:a.*b,它將會匹配最長的以a開始,以b結束的字符串。如果用它來搜索aabab的話,它會匹配整個字符串aabab。這被稱為貪婪匹配。

有時,我們更需要懶惰匹配,也就是匹配盡可能少的字符。前面給出的限定符都可以被轉化為懶惰匹配模式,隻要在它後面加上一個問號?。這樣.*?就意味著匹配任意數量的重復,但是在能使整個匹配成功的前提下使用最少的重復。

a.*?b匹配最短的,以a開始,以b結束的字符串。如果把它應用於aabab的話,它會匹配aab和ab。

public static void main(String[] args) {
        String str = "北京市(海淀區)(朝陽區)";
        String paternStr = ".*(?=\\()";
        Pattern pattern = Pattern.compile(paternStr);
        Matcher matcher = pattern.matcher(str);
        if (matcher.find()) {
            System.out.println(matcher.group(0));
        }
}

上述方法的輸出為:北京市(海淀區)

public static void main(String[] args) {
        String str = "北京市(海淀區)(朝陽區)";
        String paternStr = ".*?(?=\\()";
        Pattern pattern = Pattern.compile(paternStr);
        Matcher matcher = pattern.matcher(str);
        if (matcher.find()) {
            System.out.println(matcher.group(0));
        }
}

上述方法輸出:北京市

總結

到此這篇關於Java正則表達式之Pattern類的文章就介紹到這瞭,更多相關Java正則表達式Pattern類內容請搜索WalkonNet以前的文章或繼續瀏覽下面的相關文章希望大傢以後多多支持WalkonNet!

推薦閱讀: