java之Matcher類詳解


在JDK 1.4中,Java增加了對正則表達式的支持。

java與正則相關的工具主要在java.util.regex包中;此包中主要有兩個類:PatternMatcher


Matcher 

聲明:public final class Matcher extends Object implements MatchResult

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

含義:匹配器類,通過解釋 Pattern 對 character sequence 執行匹配操作的引擎。

注意:此類的實例用於多個並發線程是不安全的。


字段:

    /**
     * The Pattern object that created this Matcher.創建此對象的模式匹配。
     */
    Pattern parentPattern;

    /**
     * The storage used by groups. They may contain invalid values if
     * a group was skipped during the matching.組使用的存儲。如果在匹配過程中跳過一個組,它們可能包含無效的值。
     */
    int[] groups;

    /**
     * The range within the sequence that is to be matched. Anchors
     * will match at these "hard" boundaries. Changing the region
     * changes these values.要匹配的序列中的范圍。
     */
    int from, to;

    /**
     * Lookbehind uses this value to ensure that the subexpression
     * match ends at the point where the lookbehind was encountered.
     */
    int lookbehindTo;

    /**
     * The original string being matched.匹配的目的字符串。
     */
    CharSequence text;

    /**
     * Matcher state used by the last node. NOANCHOR is used when a
     * match does not have to consume all of the input. ENDANCHOR is
     * the mode used for matching all the input. NOANCHOR表示不必匹配所有的輸入;ENDANCHOR表示必須匹配所有的輸入。
     */
    static final int ENDANCHOR = 1;
    static final int NOANCHOR = 0;
    int acceptMode = NOANCHOR;

    /**
     * The range of string that last matched the pattern. If the last
     * match failed then first is -1; last initially holds 0 then it
     * holds the index of the end of the last match (which is where the
     * next search starts).最后一個匹配模式的字符串的范圍。
     */
    int first = -1, last = 0;

    /**
     * The end index of what matched in the last match operation.在最后一次匹配操作中匹配的結束索引。
     */
    int oldLast = -1;

    /**
     * The index of the last position appended in a substitution.追加在替換中的最后位置的索引。
     */
    int lastAppendPosition = 0;

    /**
     * Storage used by nodes to tell what repetition they are on in
     * a pattern, and where groups begin. The nodes themselves are stateless,
     * so they rely on this field to hold state during a match.
     */
    int[] locals;

    /**
     * Boolean indicating whether or not more input could change
     * the results of the last match. 
     * 
     * If hitEnd is true, and a match was found, then more input
     * might cause a different match to be found.
     * If hitEnd is true and a match was not found, then more
     * input could cause a match to be found.
     * If hitEnd is false and a match was found, then more input
     * will not change the match.
     * If hitEnd is false and a match was not found, then more
     * input will not cause a match to be found.
     */
    boolean hitEnd;

    /**
     * Boolean indicating whether or not more input could change
     * a positive match into a negative one.
     *
     * If requireEnd is true, and a match was found, then more
     * input could cause the match to be lost.
     * If requireEnd is false and a match was found, then more
     * input might change the match but the match won't be lost.
     * If a match was not found, then requireEnd has no meaning.
     */
    boolean requireEnd;

    /**
     * If transparentBounds is true then the boundaries of this
     * matcher's region are transparent to lookahead, lookbehind,
     * and boundary matching constructs that try to see beyond them.
     */
    boolean transparentBounds = false;

    /**
     * If anchoringBounds is true then the boundaries of this 
     * matcher's region match anchors such as ^ and $.
     */
    boolean anchoringBounds = true;

 


構造器

    Matcher() {
    }
    Matcher(Pattern parent, CharSequence text) {
        this.parentPattern = parent;
        this.text = text;

        // Allocate state storage
        int parentGroupCount = Math.max(parent.capturingGroupCount, 10);
        groups = new int[parentGroupCount * 2];
        locals = new int[parent.localCount];

        // Put fields into initial states
        reset();
    }

構造器有包訪問權限,可知不能在包外通過new創建Matcher對象。

如何在自定義的包中得到Matcher類的實例?

Matcher類中沒有合適的方法,查閱Pattern類有:

    public Matcher matcher(CharSequence input) {
    if (!compiled) { synchronized(this) { if (!compiled) compile(); } } Matcher m = new Matcher(this, input); return m; }

可知需要通過Pattern對象調用matcher方法來返回Matcher 類的實例。

對照Matcher構造器源碼,可知構造器將Pattern對象的引用賦於Matcher中變量parentPattern,目標字符串賦於變量text;並創建了數組groups和locals 。

數組groups是組使用的存儲。存儲的是當前匹配的各捕獲組的first和last信息。

groups[0]存儲的是組零的first,groups[1]存儲的是組零的last,groups[2]存儲的是組1的first,groups[3]存儲的是組1的last,依次類推。關於捕獲組的信息請看java之Pattern類詳解中的組和捕獲

初始化后狀態表:(具體分析見以下reset()方法)

變量 類型
first  int  -1
last  int  0
oldLast int  -1
lastAppendPosition int  0
from int  0
to int
text.length()
groups
int[]
locals[i] = -1
locals
int[]
 
locals[i] = -1
parentPattern
Pattern
構造器傳入的Pattern對象
text
CharSequence 
構造器傳入的目標字符串

 


部分方法:

1、public String toString()

返回匹配器的字符串表示形式。包含可用於調試的信息的 Matcher 字符串表示形式。未指定確切格式。 

源碼:

    public String toString() {
        StringBuffer sb = new StringBuffer();
    sb.append("java.util.regex.Matcher");
    sb.append("[pattern=" + pattern());
    sb.append(" region=");
    sb.append(regionStart() + "," + regionEnd());
        sb.append(" lastmatch=");
        if ((first >= 0) && (group() != null)) {
            sb.append(group());
        }
    sb.append("]");
    return sb.toString();
    }

測試:

        Pattern p = Pattern.compile("(\\w+)%(\\d+)");
        Matcher m = p.matcher("ab%12-cd%34");
        System.out.println(m);

打印:

java.util.regex.Matcher[pattern=(\w+)%(\d+) region=0,11 lastmatch=]

2、public Matcher reset()

 重置匹配器。

    public Matcher reset() {
        first = -1; last = 0; oldLast = -1; for(int i=0; i<groups.length; i++) groups[i] = -1; for(int i=0; i<locals.length; i++) locals[i] = -1; lastAppendPosition = 0; from = 0; to = getTextLength(); return this; }
   int getTextLength() {
        return text.length();
    }

可知reset()方法改變了變量first 、last 、oldLast、lastAppendPosition、from、to的值並將數組groups、locals初始化。

狀態變化:

變量 類型 新值
first  int  -1
last  int  0
oldLast int  -1
lastAppendPosition int  0
from int  0
to int
text.length()
groups
int[]
locals[i] = -1
locals
int[]
 
locals[i] = -1

 

測試1:

        Pattern p = Pattern.compile("(\\w+)%(\\d+)");
        Matcher m = p.matcher("ab%12-cd%34");
        if (m.find()) {
            System.out.println("開始索引:" + m.start());// 開始索引:0
            System.out.println("group():" + m.group());// group():ab%12
        }
        if (m.find()) {
            System.out.println("開始索引:" + m.start());// 開始索引:6
            System.out.println("group():" + m.group());// group():cd%34
        }

測試2:

        Pattern p = Pattern.compile("(\\w+)%(\\d+)");
        Matcher m = p.matcher("ab%12-cd%34");
        if (m.find()) {
            System.out.println("開始索引:" + m.start());// 開始索引:0
            System.out.println("group():" + m.group());// group():ab%12
        }
        m.reset();
        if (m.find()) {
            System.out.println("開始索引:" + m.start());// 開始索引:0
            System.out.println("group():" + m.group());// group():ab%12
        }

由測試1和測試2可知reset方法可將Matcher 對象狀態初始化。

3、public Matcher reset(CharSequence input)

重置此具有新輸入序列的匹配器。

   public Matcher reset(CharSequence input) {
        text = input; return reset(); }

可知此方法在reset()方法的基礎上改變了目標字符串的值。

測試:

        Pattern p = Pattern.compile("(\\w+)%(\\d+)");
        Matcher m = p.matcher("ab%12-cd%34");
        m.reset("ef%56-gh%78");
        while (m.find()) {
            System.out.println("group():" + m.group());
        }

打印:

group():ef%56
group():gh%78

4、public Pattern pattern()

返回由此匹配器解釋的模式。

源碼:

    public Pattern pattern() {
        return parentPattern; }

pattern()返回parentPattern,即構造器傳入的Pattern對象。

5、public int groupCount()

返回此匹配器模式中的捕獲組數。根據慣例,零組表示整個模式。它不包括在此計數中。 

測試:

        Pattern p = Pattern.compile("(\\w+)%(\\d+)");
        Matcher m = p.matcher("ab%12-cd%34");
        System.out.println(m.groupCount());// 2

6、public String group()

返回當前查找而獲得的與組匹配的所有子串內容。

查看group()源碼:

    public String group() {
        return group(0);
    }

可知group()實際調用了group(int group)方法,參數group為0。組零表示整個模式。

7public String group(int group)

返回當前查找而獲得的與組匹配的所有子串內容。

8、public int start()

返回當前匹配的子串的第一個字符在目標字符串中的索引位置 。

源碼:

    public int start() {
        if (first < 0)
            throw new IllegalStateException("No match available");
        return first;
    }

可知start()方法返回的是匹配器的狀態first。

9、public int start(int group)

返回當前匹配的指定組中的子串的第一個字符在目標字符串中的索引位置 。

10、public int end()

返回當前匹配的子串的最后一個字符的下一個位置在目標字符串中的索引位置 。

源碼:

    public int end() {
        if (first < 0)
            throw new IllegalStateException("No match available");
        return last;
    }

可知end()方法返回的是匹配器的狀態last。

11、public int end(int group)

返回當前匹配的的指定組中的子串的最后一個字符的下一個位置在目標字符串中的索引位置 。

12、public boolean find()

在目標字符串里查找下一個匹配子串。如果匹配成功,則可以通過 startend 和 group 方法獲取更多信息。 

源碼:

 

    public boolean find() {
        int nextSearchIndex = last;
        if (nextSearchIndex == first)
            nextSearchIndex++;

        // If next search starts before region, start it at region
        if (nextSearchIndex < from)
            nextSearchIndex = from;

        // If next search starts beyond region then it fails
        if (nextSearchIndex > to) {
            for (int i = 0; i < groups.length; i++)
                groups[i] = -1;
            return false;
        }
        return search(nextSearchIndex);
    }

從源碼中可知nextSearchIndex為下次查找匹配的開始位置;nextSearchIndex的值有三次判定:

1、last==first時,nextSearchIndex++;

2、nextSearchIndex<from時,nextSearchIndex=from;

3、nextSearchIndex>to時,return false;

可通過region(int start,int end)方法修改from和to,以此來影響下次查找匹配的開始位置。

注意:此方法會改變匹配器的狀態:first、last和oldLast。

        Pattern p = Pattern.compile("(\\w+)%(\\d+)");
        Matcher m = p.matcher("ab%12-cd%34");
        while (m.find()) {
            System.out.println("group():" + m.group());
            System.out.println("start():" + m.start());
            System.out.println("end():" + m.end());
            System.out.println("group(1):" + m.group(1));
            System.out.println("start(1):" + m.start(1));
            System.out.println("end(1):" + m.end(1));
            System.out.println("group(2):" + m.group(2));
            System.out.println("start(2):" + m.start(2));
            System.out.println("end(2):" + m.end(2));
            System.out.println();
        }

打印:

group():ab%12
start():0
end():5
group(1):ab
start(1):0
end(1):2
group(2):12
start(2):3
end(2):5

group():cd%34
start():6
end():11
group(1):cd
start(1):6
end(1):8
group(2):34
start(2):9
end(2):11

可知find()方法匹配了兩個子串:ab%12和cd%34每個子串有2組。

13、public boolean find(int start)

重置此匹配器,然后嘗試查找匹配該模式,從指定的位置開始查找下一個匹配的子串。如果匹配成功,則可以通過 startend 和 group 方法獲取更多信息。

注意:此方法會改變匹配器的轉態。

源碼: 

    public boolean find(int start) {
        int limit = getTextLength();
        if ((start < 0) || (start > limit))
            throw new IndexOutOfBoundsException("Illegal start index");
        reset();
        return search(start);
    }

從源碼可知此方法首先重置匹配器,然后搜索匹配,下次查找匹配的開始位置為指定的start參數。

測試:

        Pattern p = Pattern.compile("(\\w+)%(\\d+)");
        Matcher m = p.matcher("ab%12-cd%34");
        if (m.find(1)) {
            System.out.println("開始索引:" + m.start());// 開始索引:1
            System.out.println("group():" + m.group());// group():b%12
        }
        if (m.find(0)) {
            System.out.println("開始索引:" + m.start());// 開始索引:0
            System.out.println("group():" + m.group());// group():ab%12
        }
        if (m.find()) {
            System.out.println("開始索引:" + m.start());// 開始索引:6
            System.out.println("group():" + m.group());// group():cd%34
        }

當有m.find(1)時,重置匹配器,從索引1處開始匹配,匹配的子串為“b%12”;

當有m.find(0)時,重置匹配器,從索引0處開始匹配,匹配的子串為“ab%12”;

當有m.find()時,並沒有重置匹配器,從索引6處開始匹配,匹配的子串為“cd%34”;

14、public int regionStart()

報告此匹配器區域的開始索引。

源碼:

    public int regionStart() {
        return from;
    }

可知end()方法返回的是匹配器的狀態from。

15、public int regionEnd()

報告此匹配器區域的結束索引(不包括)。

源碼:

    public int regionEnd() {
        return to;
    }

可知end()方法返回的是匹配器的狀態to。

16、public Matcher region(int start,int end)

設置此匹配器的區域限制。重置匹配器,然后設置區域,使其從 start 參數指定的索引開始,到 end 參數指定的索引結束(不包括end索引處的字符)。 

    public Matcher region(int start, int end) {
        if ((start < 0) || (start > getTextLength()))
            throw new IndexOutOfBoundsException("start");
        if ((end < 0) || (end > getTextLength()))
            throw new IndexOutOfBoundsException("end");
        if (start > end)
            throw new IndexOutOfBoundsException("start > end");
        reset();
        from = start;
        to = end;
        return this;
    }

從源代碼中可知region方法首先調用reset()重置,然后對from 和to賦值,來設置匹配的目的字符串的范圍。

測試:

        Pattern p = Pattern.compile("(\\w+)%(\\d+)");
        Matcher m = p.matcher("ab%12-cd%34");
        m.region(0, 4);    
        while (m.find()) {
            System.out.println("group():" + m.group());
            System.out.println("regionStart():" + m.regionStart());
            System.out.println("regionEnd():" + m.regionEnd());
        }

打印:

group():ab%1
regionStart():0
regionEnd():4

17public boolean lookingAt()
從目標字符串開始位置進行匹配。只有在有匹配匹配的某一子串中包含目標字符串第一個字符的情況下才會返回true。

源碼:

    public boolean lookingAt() {
        return match(from, NOANCHOR);
    }

從源碼中可知下次查找匹配的開始位置為from,可通過region(int start,int end)方法修改from的值。

測試:

        Pattern p = Pattern.compile("(\\w+)%(\\d+)");
        Matcher m = p.matcher("ab%12-cd%34");
        System.out.println(m.lookingAt());// true
        m = p.matcher("%ab%12-cd%34");
        System.out.println(m.lookingAt());// false

18public boolean matches()
只有完全匹配時才會返回true。

源碼:

    public boolean matches() {
        return match(from, ENDANCHOR);
    }

對比上一個方法lookingAt(),從代碼上看差別很小,調用的match方法只有一個參數不一樣;lookingAt中使用的NOANCHOR,而matches中使用的ENDANCHOR。

NOANCHOR表示不必匹配所有的輸入;ENDANCHOR表示必須匹配所有的輸入。

測試:

        Pattern p = Pattern.compile("(\\w+)%(\\d+)");
        Matcher m = p.matcher("%ab%12");
        System.out.println(m.matches());// false
        m = p.matcher("ab%12%");
        System.out.println(m.matches());// false
        m = p.matcher("ab%12");
        System.out.println(m.matches());// true

19public Matcher appendReplacement(StringBuffer sb, String replacement) 

將當前匹配子串替換為指定字符串,並將從上次匹配結束后到本次匹配結束后之間的字符串添加到一個StringBuffer對象中,最后返回其字符串表示形式。

注意:對於最后一次匹配,其后的字符串並沒有添加入StringBuffer對象中,若需要這部分的內容需要使用appendTail方法

20public StringBuffer appendTail(StringBuffer sb)
將最后一次匹配工作后剩余的字符串添加到一個StringBuffer對象里。 

源碼:

    public StringBuffer appendTail(StringBuffer sb) {
        sb.append(getSubSequence(lastAppendPosition, getTextLength()).toString());
    return sb;
    }

查看源碼有getSubSequence(lastAppendPosition, getTextLength()),即獲取從lastAppendPosition索引處開始,到目的字符串結束索引處之間的子串。

lastAppendPosition為何值?

查閱Matcher類代碼后,發現appendReplacement方法中有:

    lastAppendPosition = last;

last即目前最后一次匹配結束后的索引。

測試:

        Pattern p = Pattern.compile("(\\w+)%(\\d+)");
        Matcher m = p.matcher("前ab%12中cd%34后");
        StringBuffer s = new StringBuffer();
        while (m.find()) {
            m.appendReplacement(s, "app");
        }
        System.out.println(s);// 前app中app
        m.appendTail(s);
        System.out.println(s);// 前app中app后

21、public String replaceAll(String replacement)

將匹配的子串用指定的字符串替換。

    public String replaceAll(String replacement) {
        reset();
        boolean result = find();
        if (result) {
            StringBuffer sb = new StringBuffer();
            do {
                appendReplacement(sb, replacement);
                result = find();
            } while (result);
            appendTail(sb);
            return sb.toString();
        }
        return text.toString();
    }

查看源碼可知此方法首先重置匹配器,然后判斷是否有匹配,若有,則創建StringBuffer 對象,然后循環調用appendReplacement方法進行替換,最后調用 appendTail方法並返回StringBuffer 對象的字符串形式。

        Pattern p = Pattern.compile("(\\w+)%(\\d+)");
        Matcher m = p.matcher("ab%12-cd%34");
        StringBuffer s = new StringBuffer();
        System.out.println(m.replaceAll("app"));// app-app

22public String replaceFirst(String replacement)

將匹配的第一個子串用指定的字符串替換。

    public String replaceFirst(String replacement) {
        if (replacement == null)
            throw new NullPointerException("replacement");
        StringBuffer sb = new StringBuffer();
        reset();
        if (find())
            appendReplacement(sb, replacement);
        appendTail(sb);
        return sb.toString();
    }

查看源碼可知此方法其實是 replaceAll方法的減配版本,只對第一次匹配做了替換。

測試:

        Pattern p = Pattern.compile("(\\w+)%(\\d+)");
        Matcher m = p.matcher("ab%12-cd%34");
        StringBuffer s = new StringBuffer();
        System.out.println(m.replaceFirst("app"));// app-cd%34

23、public Matcher usePattern(Pattern newPattern)

更改匹配器的匹配模式。 

測試:

    public static void main(String[] args) {
        Pattern p = Pattern.compile("[a-z]+");
        Matcher m = p.matcher("111aaa222");
        System.out.println(piPei(m));// (模式[a-z]+):匹配子串:aaa;開始位置:3;結束位置:6;
        m.usePattern(Pattern.compile("\\d+"));
        System.out.println(piPei(m));// (模式\d+):匹配子串:222;開始位置:6;結束位置:9;
    }

    public static String piPei(Matcher m) {
        StringBuffer s = new StringBuffer();
        while (m.find()) {
            s.append("匹配子串:" + m.group() + ";");
            s.append("開始位置:" + m.start() + ";");
            s.append("結束位置:" + m.end() + ";");
        }
        if (s.length() == 0) {
            s.append("沒有匹配到!");
        }
        s.insert(0, "(模式" + m.pattern().pattern() + "):");
        return s.toString();
    }

可以看到更改匹配模式為"\\d+"只匹配到了"222",若需要匹配所有數字字符,應對匹配器初始化。

        Pattern p = Pattern.compile("[a-z]+");
        Matcher m = p.matcher("111aaa222");
        System.out.println(piPei(m));// (模式[a-z]+):匹配子串:aaa;開始位置:3;結束位置:6;
        m.usePattern(Pattern.compile("\\d+"));
        m.reset();
        System.out.println(piPei(m));// (模式\d+):匹配子串:111;開始位置:0;結束位置:3;匹配子串:222;開始位置:6;結束位置:9;

 


更多與正則表達式相關內容:

java正則規則表

java正則表達式之Greedy、Reluctant和Possessive

java之Pattern類詳解


免責聲明!

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



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