Java正则(3)— Matcher 详解

☪ Matcher 概述


声明:public final class Matcher extends Object implements MatchResult

Matcher 类有final 修饰,可知他不能被子类继承。

含义:匹配器类,通过解释 Pattern 对 character sequence 执行匹配操作的引擎。

注意:此类的实例用于多个并发线程是不安全的。

☪ Matcher 方法


方法 说明
public Pattern pattern() ⇢⇢⇢⇢⇢⇢⇢⇢⇢⇢⇢⇢⇢⇢⇢⇢⇢⇢⇢⇢⇢⇢ 返回创建Matcher对象的Pattern对象。
public MatchResult toMatchResult() 将匹配结果以MatchResult的形式返回
public Matcher usePattern(Pattern newPattern) 修改Matcher对象的Pattern,用以进行新的模式匹配。
public Matcher reset() 重置匹配器的状态。
public Matcher reset(CharSequence input) 重置匹配器的状态,重置目标字符串的值为input。
public int start() 返回当前匹配到的字符串在原目标字符串中的起始索引位置
public int start(int group) 返回当前匹配到的字符串中group组在目标字符串的起始索引位置
public int end() 返回当前匹配的字符串的最后一个字符在原目标字符串中的offset(偏移量),这个需要大家注意一下。
public int end(int group) 返回当前匹配的字符串中group组的最后一个字符在原目标字符串中的offset(偏移量),这个需要大家注意一下。
public String group() 返回匹配到的字符串,结合find函数使用。
public String group(int group) 返回匹配到的字符串中的group组的字符串。
public String group(String name) 返回被named-capturing组捕获的字符串,关于named-capturing group(命名捕获组)是JDK1.7新增的功能,可以将正则表达式中的组进行命名。
public int groupCount() 返回当前Matcher对象捕获的组的个数。
public boolean matches() 将整个目标字符串与正则表达式进行匹配,只有完全匹配才能返回true,否则false。
public boolean find() 对目标字符串进行正则匹配,通过while可以多次执行find方法,获取多次的匹配结果,代码编写方式类似于iterator.next()。
public boolean find(int start) 在指定的索引位置对目标字符串进行正则匹配。
public boolean lookingAt() 目标字符串的起始字符串与正则表达式匹配返回true,否则返回false。
public static String quoteReplacement(String s) 返回字符串s字面意义的替代字符串。
public Matcher appendReplacement(StringBuffer sb, String replacement) 向sb中追加replacement字符串,replacement字符串中可以包含匹配器中的分组参数,如1,2。
public StringBuffer appendTail(StringBuffer sb) 将Matcher匹配后的尾部字符串追加至sb中。
public String replaceAll(String replacement) 将目标字符串中所有满足正则匹配的字符串替换为replacement。
public String replaceFirst(String replacement) 将目标字符串中第一个满足正则匹配的字符串替换为replacement。
public Matcher region(int start, int end) 设置目标字符串的匹配范围。
public int regionStart() 返回匹配器区域的起始点索引位置。
public int regionEnd() 返回匹配器区域的结束点索引位置。
public boolean hasTransparentBounds() TransparentBounds标志位:查询TransparentBounds标志位true|false,此标志位默认为false。如果匹配范围不是整个目标字符串,而是一部分,那么如果此标志位设为true的话,则允许顺序环视、逆序环视以及单词分界符超越匹配范围边界的设置,匹配目标字符串的其他部分,也就是可以稍微有越界行为。可以通过useTransparentBounds()进行修改设置。
public Matcher useTransparentBounds(boolean b) 设置TransparentBounds标志位的值true|false
public boolean hasAnchoringBounds() AnchoringBounds标志位:查询AnchoringBounds标志位的值,此标志位默认为true。在应用正则表达式的时候,我们可以指定目标字符串的检索范围,也就是说在目标字符串的子字符串中应用正则表达式。但此时会有一个问题,那就是 ^ 和 $ 应该匹配整个字符串的开头和结尾呢? 还是检索范围的起始和结束位置呢?Java 为我们提供了足够的灵活性,我们可以通过下面的方法来查看和设置,默认值是匹配检索范围的起始和结束位置。
public Matcher useAnchoringBounds(boolean b) 设置AnchoringBounds标志位的值true | false
public boolean hitEnd()
public boolean requireEnd()
boolean search(int from)
boolean match(int from, int anchor)
int getTextLength() 返回目标字符串的长度。
CharSequence getSubSequence(int beginIndex, int endIndex) 获取目标字符串的子字符串。
char charAt(int i) 返回目标字符串中索引为i的字符

☪ 方法示例


❦ Matcher 构造方法

    /**
     * No default constructor.
     */
    Matcher() {
    }

    /**
     * All matchers have the state used by Pattern during a match.
     */
    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] 存储的是组 0 的 first,groups[1] 存储的是组 0 的 last,groups[2] 存储的是组 1 的 first,groups[3] 存储的是组 1 的 last,依次类推。

初始化后状态表:(具体分析见以下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
❦ public Matcher reset():重置匹配器的状态

    public Matcher reset() {
        first = -1;
        last = 0;
        oldLast = -1;
        for(int i=0; i1;
        for(int i=0; i1;
        lastAppendPosition = 0;
        from = 0;
        to = getTextLength();
        return this;
    }

可知 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
parentPattern Pattern 构造器传入的Pattern对象
text CharSequence 构造器传入的目标字符串

测试一:

        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
        }

测试二:

        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 对象状态初始化。

❦ public Matcher reset(CharSequence input):重置匹配器的状态,重置目标字符串的值为input

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

可知此方法在reset()方法的基础上改变了目标字符串的值。

Java代码示例:

    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
❦ public String group()

查看group()源码:

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

可知 group() 实际调用了 group(int group) 方法,参数 group 为 0。组零表示整个模式。

Java代码示例:

    Pattern p = Pattern.compile("(\\w+)%(\\d+)");
    Matcher m = p.matcher("ab%12-cd%34");
    if (m.find()) {
        System.out.println(m.group());// ab%12
        System.out.println(m.group(0));// ab%12
    }
❦ Matcher.matches()、Matcher.lookingAt()、Matcher.find()

Matcher 类提供了三个匹配操作方法,三个方法均返回 boolean 类型,当匹配到时返回 true,没匹配到则返回 false 。

matches() 对整个字符串进行匹配,只有整个字符串都匹配了才返回true 。

Java代码示例:

Pattern p = Pattern.compile("\\d+");

Matcher m = p.matcher("22bb23");
System.out.println(m.matches());// 返回false,因为bb不能被\d+匹配,导致整个字符串匹配未成功.

m = p.matcher("2223");
System.out.println(m.matches());// 返回true,因为\d+匹配到了整个字符串

我们现在回头看一下Pattern.matcher(String regex,CharSequence input),它与下面这段代码等价
Pattern.compile(regex).matcher(input).matches()

lookingAt() 对前面的字符串进行匹配,只有匹配到的字符串在最前面才返回true。

Java代码示例::

Pattern p = Pattern.compile("\\d+");

Matcher m = p.matcher("22bb23");
System.out.println(m.lookingAt());// 返回true,因为\d+匹配到了前面的22

m = p.matcher("aa2223");
System.out.println(m.lookingAt());// 返回false,因为\d+不能匹配前面的aa

find()对字符串进行匹配,匹配到的字符串可以在任何位置。

Java代码示例:

Pattern p = Pattern.compile("\\d+");

Matcher m = p.matcher("22bb23");
System.out.println(m.find());// 返回true

m = p.matcher("aa2223");
System.out.println(m.find());// 返回true

m = p.matcher("aa2223bb");
System.out.println(m.find());// 返回true

m = p.matcher("aabb");
System.out.println(m.find());// 返回false
❦ Mathcer.start()、Matcher.end()、Matcher.group()

当使用matches()lookingAt()find()执行匹配操作后,就可以利用以上三个方法得到更详细的信息:

  • start()返回匹配到的子字符串的第一个字符在原字符串中的索引位置;
  • end()返回匹配到的子字符串的最后一个字符在原字符串中的索引位置;
  • group()返回匹配到的子字符串。

Java代码示例:

Pattern p = Pattern.compile("\\d+");                                                                    

System.out.println("==========find 方法==========");                                                      
Matcher m = p.matcher("aaa2223bb");                                                                     
System.out.println(m.find());// 匹配2223                                                                  
System.out.println(m.start());// 返回3                                                                    
System.out.println(m.end());// 返回7,返回的是2223后的索引号                                                        
System.out.println(m.group());// 返回2223                                                                 

System.out.println("==========lookingAt 方法==========");                                                 
m = p.matcher("2223bb");                                                                                
System.out.println(m.lookingAt()); // 匹配2223                                                            
System.out.println(m.start()); // 返回0,由于lookingAt()只能匹配前面的字符串,所以当使用lookingAt()匹配时,start()方法总是返回0        
System.out.println(m.end()); // 返回4                                                                     
System.out.println(m.group()); // 返回2223                                                                

System.out.println("==========matches 方法==========");                                                   
m = p.matcher("2223bb");                                                                                
System.out.println(m.matches()); // 匹配整个字符串,返回false,所以后边的执行会报错                                          
System.out.println(m.start());                                                                          
System.out.println(m.end());                                                                            
System.out.println(m.group());                                                                          

说了这么多,相信大家都明白了以上几个方法的使用,该说说正则表达式的分组在java中是怎么使用的。

❦ start(int i)、end(int i),group(int i)、groupCount()

start()end()group()均有一个重载方法,它们是start(int i)end(int i)group(int i)专用于分组操作,Mathcer 类还有一个groupCount()用于返回有多少组。

Java代码示例:

Pattern p = Pattern.compile("([a-z]+)(\\d+)");                         
Matcher m = p.matcher("aaa2223bb");                                    

System.out.println(m.find()); // 匹配aaa2223                             
System.out.println(m.groupCount()); // 返回2,因为有2组                       
System.out.println(m.start(1)); // 返回0 返回第一组匹配到的子字符串的第一个字符在原字符串中的索引号   
System.out.println(m.start(2)); // 返回3 返回第二组匹配到的子字符串的第一个字符在原字符串中的索引号   
System.out.println(m.end(1)); // 返回3 返回第一组匹配到的子字符串的最后一个字符在原字符串中的索引号    
System.out.println(m.end(2)); // 返回7                                   
System.out.println(m.group(1)); // 返回aaa,返回第一组匹配到的子字符串                 
System.out.println(m.group(2)); // 返回2223,返回第二组匹配到的子字符串                

现在我们使用一下稍微高级点的正则匹配操作,例如有一段文本,里面有很多数字,而且这些数字是分开的,我们现在要将文本中所有数字都取出来。利用java的正则操作是那么的简单。

Java代码示例:

Pattern p = Pattern.compile("\\d+");                                       
Matcher m = p.matcher("我的QQ是:456456 我的电话是:0532214 我的邮箱是:[email protected]");  
while (m.find()) {                                                         
    System.out.println(m.group());                                         
}                                                                          

输出:

456456
0532214
123

如将以上 while() 循环替换成:

Pattern p = Pattern.compile("\\d+");                                       
Matcher m = p.matcher("我的QQ是:456456 我的电话是:0532214 我的邮箱是:[email protected]");  
while (m.find()) {                                                         
    System.out.println(m.group());                                         
    System.out.print("start:" + m.start());                                
    System.out.println(" end:" + m.end());                                 
}                                                                          

则输出:

456456
start:6 end:12
0532214
start:19 end:26
123
start:36 end:39

现在大家应该知道,每次执行匹配操作后start()end()group()三个方法的值都会改变,改变成匹配到的子字符串的信息,以及它们的重载方法,也会改变成相应的信息。

注意:只有当匹配操作成功,才可以使用start()end()group()三个方法,否则会抛出java.lang.IllegalStateException,也就是当matches()lookingAt()find()其中任意一个方法返回 true 时,才可以使用。

❦ Mathcer.replaceAll(String replacement) 和 Mathcer.replaceFirst(String replacement)

Mathcer.replaceAll(String replacement) 方法源码

查看源码可知此方法首先重置匹配器,然后判断是否有匹配,若有,则创建StringBuffer 对象,然后循环调用appendReplacement 方法进行替换,最后调用 appendTail 方法并返回 StringBuffer 对象的字符串形式。

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();                    
}                                              

Mathcer.replaceFirst(String replacement) 方法源码

查看源码可知此方法其实是 replaceAll方法的减配版本,只对第一次匹配做了替换。

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();                             
}                                                     

Java代码示例:

System.out.println("=============例子一============");                
Pattern pattern = Pattern.compile("Java");                         
Matcher matcher = pattern.matcher("JavaJava");                     
System.out.println(matcher.replaceAll("Python"));// 返回PythonPython 
System.out.println(matcher.replaceFirst("python"));// 返回PythonJava 

System.out.println("=============例子二============");                
pattern = Pattern.compile("(\\w+)%(\\d+)");                        
matcher = pattern.matcher("ab%12-cd%34");                          
System.out.println(matcher.replaceAll("app"));// app-app           
❦ Mathcer.appendReplacement(StringBuffer sb, String replacement) 和 Mathcer.appendTail(StringBuffer sb)

Mathcer.appendReplacement(StringBuffer sb, String replacement) 方法

将当前匹配子串替换为指定字符串,并将从上次匹配结束后到本次匹配结束后之间的字符串添加到一个StringBuffer对象中,最后返回其字符串表示形式。

注意:对于最后一次匹配,其后的字符串并没有添加入StringBuffer对象中,若需要这部分的内容需要使用appendTail方法。

Mathcer.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 即目前最后一次匹配结束后的索引。

Java代码示例:

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后          
❦ Mathcer.regionStart()、Mathcer.regionEnd()、Mathcer.region(int start,int end)

Mathcer.regionStart() 方法源码

返回匹配器区域的起始点索引位置。

public int regionStart() {
    return from;          
}                         

Mathcer.regionEnd() 方法源码

返回匹配器区域的结束点索引位置。

public int regionEnd() {
    return to;          
}                       

Mathcer.region(int start,int 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 赋值,来设置匹配的目的字符串的范围。

Java代码示例:

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
❦ Mathcer.useTransparentBounds(boolean b)

设置TransparentBounds标志位的值true|false

Java代码示例:

String regex = "\\bcar\\b";                                      
String text = "Madagascar is best seen by car or bike.";         
Matcher m = Pattern.compile(regex).matcher(text);                
m.region(7, text.length());                                      
m.useTransparentBounds(false);                                   
m.find();                                                        
System.out.println("Matches starting at character " + m.start());

m.reset();                                                       
m.useTransparentBounds(true);                                    
m.find();                                                        
System.out.println("Matches starting at character " + m.start());
Matches starting at character 7
Matches starting at character 27

\b 匹配一个字边界,即字与空格间的位置。例如,er/b匹配never中的er,但不匹配verb中的er

  • TransparentBounds = false,region区域从index=7开始,Madagascar也就是从car开始,匹配器无法感知region区域外的字符,因此第一个car被匹配。
  • TransparentBounds = true,region区域从index=7开始,Madagascar也就是从car开始,匹配器可以感知region区域外的字符,因此第一个car不被匹配。
❦ Mathcer.useTransparentBounds(boolean b)

AnchoringBounds标志位:

  • 设置AnchoringBounds标志位的值,此标志位默认为true;
  • true:^ 和 $ 应该匹配检索范围的起始和结束位置;
  • false:^ 和 $ 应该匹配整个字符串的开头和结尾;

Java代码示例:

String text = "Madagascar is best seen by car or bike.";         
Matcher m = Pattern.compile("^car").matcher(text);               
m.region(7, text.length());                                      

m.useAnchoringBounds(true);                                      
m.find();                                                        
System.out.println("Matches starting at character " + m.start());

m.reset();                                                       

m.useAnchoringBounds(false);                                     
m.find();                                                        
System.out.println("Matches starting at character " + m.start());
Matches starting at character 7
Exception in thread "main" java.lang.IllegalStateException: No match available
    at java.util.regex.Matcher.start(Matcher.java:342)
    at testRegex.aaa.main(aaa.java:21)
❦ Mathcer.hitEnd() 和 Mathcer.requireEnd()

其实这两个方法没搞明白是什么意思,等有时间在搞清楚吧!!!!

String[] matcherStrs = { "1234", "1234.>.567", ">", ">.567", ">=", ">=.567", "oops" };    
Pattern pattern = Pattern.compile("\\d+\\b|[><]=?");                                      
Matcher m = null;                                                                         
for (String matcherStr : matcherStrs) {                                                   
    m = pattern.matcher(matcherStr);                                                      
    boolean find_result = true;                                                           
    if (find_result = m.find()) {                                                         
        System.out.println(String.format("正则是%s,匹配文本是%s,匹配是否成功:%s,匹配结果是%s",               
                m.pattern(), matcherStr, find_result, find_result ? m.group() : "匹配失败")); 
        System.out.println("hitEnd() is " + m.hitEnd());                                  
        System.out.println("requireEnd() is " + m.requireEnd());                          
        System.out.println();                                                             
    }                                                                                     
}                                                                                         
正则是\d+\b|[><]=?,匹配文本是1234,匹配是否成功:true,匹配结果是1234
hitEnd() is true
requireEnd() is true

正则是\d+\b|[><]=?,匹配文本是1234.>.567,匹配是否成功:true,匹配结果是1234
hitEnd() is false
requireEnd() is false

正则是\d+\b|[><]=?,匹配文本是>,匹配是否成功:true,匹配结果是>
hitEnd() is true
requireEnd() is false

正则是\d+\b|[><]=?,匹配文本是>.567,匹配是否成功:true,匹配结果是>
hitEnd() is false
requireEnd() is false

正则是\d+\b|[><]=?,匹配文本是>=,匹配是否成功:true,匹配结果是>=
hitEnd() is false
requireEnd() is false

正则是\d+\b|[><]=?,匹配文本是>=.567,匹配是否成功:true,匹配结果是>=
hitEnd() is false
requireEnd() is false

你可能感兴趣的:(正则表达式,Matcher,正则表达式)