相关链接:

本文不讲解正则表达式相关的知识内容,如对这块知识不太了解的同学,请先移步至正则表达式 – 教程 | 菜鸟教程学习相关知识;本文主要讲解在Java中如何利用正则表达式进行模式匹配相关知识内容。

在Java中使用原生JDK实现正则表达式的匹配,不可避免的我们需要使用到java.util.regex包下的两个类PatternMatcher

初识模式匹配

我们的一般用法,就是先定义一个正则串匹配串,然后看匹配串中是否有满足正则串条件的字符序列;如下:

public class Demo1 {
    public static void main(String[] args) {
        //[a-z]表示a~z之间的任何一个字符, {3}表示3个字符, 意思是匹配一个长度为3, 并且每个字符属于a~z的字符串
        Pattern p = Pattern.compile("[a-z]{3}");
        Matcher m = p.matcher("abc");
        System.out.println(m.matches());
    }
}
//输出结果
true
  • 如果要深究正则表达式背后的原理, 会涉及编译原理中自动机等知识, 此处不展开描述. 为了达到通俗易懂, 这里用较为形象的语言描述.
  • Pattern可以理解为一个模式, 字符串需要与某种模式进行匹配. 比如Demo2中, 我们定义的模式是一个长度为3的字符串, 其中每个字符必须是a~z中的一个.
  • 我们看到创建Pattern对象时调用的是Pattern类中的compile方法, 也就是说对我们传入的正则表达式编译后得到一个模式对象. 而这个经过编译后模式对象, 会使得正则表达式使用效率会大大提高, 并且作为一个常量, 它可以安全地供多个线程并发使用.
  • Matcher可以理解为模式匹配某个字符串后产生的结果. 字符串和某个模式匹配后可能会产生很多个结果, 这个会在后面的例子中讲解.
  • 最后当我们调用m.matches()时就会返回完整字符串与模式匹配的结果
  • 上面的三行代码可以简化为一行代码 System.out.println("abc".matches("[a-z]{3}"));
  • 但是如果一个正则表达式需要被重复匹配, 这种写法效率较低.

完全匹配和部分匹配

通过上面的Demo1样例中,我们以经初步了解了Java对正则表达式匹配的简单使用;我们再来看下面这个Demo2样例

public class Demo2 {
    public static void main(String[] args) {
        //[a-z]表示a~z之间的任何一个字符, {3}表示3个字符, 意思是匹配一个长度为3, 并且每个字符属于a~z的字符串
        Pattern p = Pattern.compile("[a-z]{3}"); // 正则串 - "[a-z]{3}"
        Matcher m = p.matcher("abcd");  // 模式串 - "abcd"
        System.out.println(m.matches());
    }
}

大家可以猜测这个结果会输出什么呢?此处停留3秒~~~

大家可以复制这段代码运行一遍,会惊奇的发现,现在输出的结果变为了false而不再是原来的true了;

为什么出现上面这个情况呢?我们仅仅是在匹配串里面多加了一个d字符,为什么就会导致匹配不上了呢?

要回答上面的问题,就需要了解完全匹配和部分匹配的区别;

  • 完全匹配整个匹配串全都匹配正则串
  • 部分匹配匹配串有子串匹配正则串

使用Matcher.matches()方法判断匹配串是否匹配到正则串是一种完全匹配的判断,即需要完全匹配的情况下才会返回true;而如果我们想如果部分匹配到了就返回true,我们就需要使用Matcher.find()来判断是否部分匹配到结果

public class Demo3 {
    public static void main(String[] args) {
        //[a-z]表示a~z之间的任何一个字符, {3}表示3个字符, 意思是匹配一个长度为3, 并且每个字符属于a~z的字符串
        Pattern p = Pattern.compile("[a-z]{3}"); // 正则串 - "[a-z]{3}"
        Matcher m = p.matcher("abcd");  // 模式串 - "abcd"
        System.out.println(m.find());
    }
}

执行Demo3样例会发现现在打印出的结果过即是true

起始/终止标识符

public class Demo4 {
    public static void main(String[] args) {
        //[a-z]表示a~z之间的任何一个字符, {3}表示3个字符, 意思是匹配一个长度为3, 并且每个字符属于a~z的字符串
        Pattern p = Pattern.compile("^[a-z]{3}$"); // 正则串 - "^[a-z]{3}$"
        Matcher m = p.matcher("abcd");  // 模式串 - "abcd"
        System.out.println(m.find());
    }
}

执行Demo4样例会发现现在打印出的结果又变为了false

这是因为我们在正则串-^[a-z]{3}$的前后加入了起始^/终止$标识符;等价于要全匹配,才能返回true

我们改为Demo5样例,执行

public class Demo5 {
    public static void main(String[] args) {
        //[a-z]表示a~z之间的任何一个字符, {3}表示3个字符, 意思是匹配一个长度为3, 并且每个字符属于a~z的字符串
        Pattern p = Pattern.compile("^[a-z]{3}$"); // 正则串 - "^[a-z]{3}$"
        Matcher m = p.matcher("abc");  // 模式串 - "abcd"
        System.out.println(m.find());
    }
}

会发现现在的结果又变为了true,因为现在是匹配串正则串完全匹配

分组匹配

以下是一段Cisco防火墙的配置

......
access-list 1 line 2 extended permit object TCP-33 object-group oa-ip object-group oa-test (hitcnt=0) 0x8f05ca88 
  access-list 1 line 2 extended permit tcp host 10.1.1.4 host 20.1.1.1 range 33 33 (hitcnt=0) 0xa1bf013a 
  access-list 1 line 2 extended permit tcp host 10.1.1.4 host 20.1.1.5 range 33 33 (hitcnt=0) 0x3372620c 
  access-list 1 line 2 extended permit tcp host 10.1.1.5 host 20.1.1.1 range 33 33 (hitcnt=0) 0xf907dcff 
  access-list 1 line 2 extended permit tcp host 10.1.1.5 host 20.1.1.5 range 33 33 (hitcnt=0) 0x73b10004 
  access-list 1 line 2 extended permit tcp host 10.1.1.1 host 20.1.1.1 range 33 33 (hitcnt=0) 0x964c8122 
  access-list 1 line 2 extended permit tcp host 10.1.1.1 host 20.1.1.5 range 33 33 (hitcnt=0) 0xf6fd9fc9 
  access-list 1 line 3 extended permit object TCP-33333 object WS-172.21.2.10_32 object WS-11.1.1.10_32 (hitcnt=0) (inactive) 0xb206976e 
  access-list 1 line 3 extended permit tcp host 172.21.2.10 host 11.1.1.10 range 33333 33333 (hitcnt=0) 0xb206976e 
access-list 2; 1 elements; name hash: 0x4ba440f6
access-list 2 line 1 extended permit ip any any (hitcnt=0) 0x86268c7e 
......

假设我们现在要查找其中access-list 1 line 3 extended permit object TCP-33333 object WS-172.21.2.10_32 object WS-11.1.1.10_32 (hitcnt=0) 0xb206976e 策略的HASH值0xb206976e

public void groutMatch() throws IOException {
        String raw = "上面Cisco防火墙的配置"

        String policy = "access-list 1 line 3 extended permit object TCP-33333 object WS-172.21.2.10_32 object WS-11.1.1.10_32";

    	// 转换特殊字符`.`
        String replacedPolicy = policy.replaceAll("\\.", "\\\\.");

        // 全匹配
//        String policyTemp = "^[\\s\\S]*?(policy)(\\s*?)(\\(\\S*?\\)\\s)(\\(\\S*?\\)\\s)*?(0x\\S+)[\\s\\S]*$";
    
        // 部分匹配
        String policyTemp = "(policy)(\\s*?)(\\(\\S*?\\)\\s)(\\(\\S*?\\)\\s)*?(0x\\S+)";

        policyTemp = policyTemp.replace("policy", replacedPolicy);

        Pattern CISCO_REGEX = Pattern.compile(policyTemp);

        Matcher matcher = CISCO_REGEX.matcher(raw);
        if (matcher.find()) {
            // matcher.group(index)
            System.out.print(matcher.group(1));
            System.out.print(matcher.group(2));
            System.out.print(matcher.group(3));
            System.out.print(StringUtils.isBlank(matcher.group(4))? "" : matcher.group(4));
            System.out.println(matcher.group(5));
        } else {
            System.out.println("not match");
        }

    }

matcher.group(index)其中group是针对括号()来说的,group(0)就是指的整个串,group(1) 指的是第一个括号里的东西,group(2)指的第二个括号里的东西。

对应上面的样例,我们可以拆解为如下:

***注意:***这里我们可以发现第3个括号中的正则表达式和第4个括号正则表达式的内容是一样的;理论上我们可以不要第3个括号的正则表达式因为第4个括号正则表达式后面跟随了*?可以匹配0-N个相同的正则表达式内容;但是由于当有多匹配*/+的时候,通过group(index)获取匹配内容的时候只能拿到最后一个匹配的内容;但是group(0)还是可以拿到匹配的所有内容

public void groutMatch() throws IOException {
        String raw = "上面Cisco防火墙的配置"

        String policy = "access-list 1 line 3 extended permit object TCP-33333 object WS-172.21.2.10_32 object WS-11.1.1.10_32";

    	// 转换特殊字符`.`
        String replacedPolicy = policy.replaceAll("\\.", "\\\\.");
    
        // 部分匹配
        String policyTemp = "(policy)(\s*?)(\(\S*?\)\s)*?(0x\S+)";

        policyTemp = policyTemp.replace("policy", replacedPolicy);

        Pattern CISCO_REGEX = Pattern.compile(policyTemp);

        Matcher matcher = CISCO_REGEX.matcher(raw);
        if (matcher.find()) {
            // matcher.group(index)
            System.out.print(matcher.group(1));
            System.out.print(matcher.group(2));
            System.out.print(matcher.group(3));
            System.out.print(StringUtils.isBlank(matcher.group(4))? "" : matcher.group(4));
            
            System.out.println();
            
            System.out.println(matcher.group(0));
        } else {
            System.out.println("not match");
        }

    }

输出如下结果:

access-list 1 line 3 extended permit object TCP-33333 object WS-172.21.2.10_32 object WS-11.1.1.10_32 (inactive) 0xb206976e

matcher.group(0)的输出结果

access-list 1 line 3 extended permit object TCP-33333 object WS-172.21.2.10_32 object WS-11.1.1.10_32 (hitcnt=0) (inactive) 0xb206976e