JAVA正则表达式进阶

文章目录

      • 正则表达式需要转义的字符
      • 正则的横向模糊匹配与纵向模糊匹配
      • 正则中的分组
        • 如何计算分组?
        • 正向引用分组
        • 反向引用分组
      • 正则中的贪婪匹配与惰性匹配
      • 正则匹配忽略大小写及区间去除
      • 匹配位置

阅读此文须有正则的基础知识,若没有请先了解正则表达式。正则表达式用于快速地匹配文本。 写正则时先局部匹配,然后再当作一个整体

正则表达式需要转义的字符

正则中用到的字符,不加转义正则会解析成特殊含义。需要转义的即在正则中代表特殊含义的字符。如下:
^、$、.、*、+、?、|、\、(、)、[、]、{、}、=、!、:、-
若要匹配以上字符在java中必须用\进行转义,否则代表特殊含义。比如String中的split方法,以点分隔。split("\\.")

正则的横向模糊匹配与纵向模糊匹配

横向模糊匹配:一个正则表达式匹配的字符串长度是不固定的,比如?,*,+,{m,n}等。
例如:ab{2,4}c。匹配的字符有(注意红色字体,很好的体现了横向这个词):

abbbc
abbbbc
abbbbbc

纵向模糊匹配:一个正则匹配的字符串,在匹配的位不确定位的值,比如\d,[a-z],\w等。
例如:a[123]b。匹配的有(注意红色字体,很好的体现了纵向这个词):
a1c
a2c
a3c

正则中的分组

  • 正则中的分组是以()来计算的,可以正向引用及反向引用分组。使正则的功能更加强大。若不引用分组在括号中的前面加?:。
    如(?:)即代表不引用分组。
  • java中使用Matcher对象的group方法获取分组的值。
  • group()即是group(0)。只要字符串匹配正则就是group(0)。
    比如字符串为:abbcd。正则为ab{2,4}c,则group()为abbc
                             abbbcd 。正则为ab{2,4}c,则group()为abbbc
    group(int m)m值为分组。
    比如正则表达式:(ab)(bc)d。有两个分组,分组1为ab,分组2为bc

如何计算分组?

给小括号分等级。先找到最外的小括号然后找它的孩子、孙子直至找完,然后找兄弟,兄弟的孩子、孙子直至找完。依次类推就能正确的计算分组,这是使用正向引用、反向引用分组的前提。分组是括号中匹配到的内容
比如正则((\d)(\d(\d)))(a)。先找到一对小括号,((\d)(\d(\d)))为分组1,即三个数字。接着找它的孩子,即(\d)、(\d(\d))分别为分组2,分组3。然后找分组3的孩子(\d)为分组4。找完之后,然后找它的兄弟,即(\a)为分组5。
如:字符串为123a。正则为上述正则。
分组1为:123
分组2为:1
分组3为:23
分组4为:3
分组5为:a

正向引用分组

使用\数字进行引用分组。比如引用分组\1。即把数字给转义代表引用的分组。正向引用是在正则表达式中引用,而反向引用是在匹配后的结果中引用。

例如匹配日期支持以下三种形式:

2020-06-20
2020/06/20
2020.06.20

想下正则:按照先局部再整体的原则
匹配年份:(\d){4}
匹配格式符号:([\\-\\./])
匹配月份:(\d){2}
匹配天数:(\d){2}
整体的正则为:(\d){4}([-./])(\d){2}([-./])(\d){2}
本以为万事大吉,但是能匹配到2020-06.20等即没控制格式符号。导致不符合正则的要求。
使用分组正则表达式改为:(\d){4}([\\-\\./])(\d){2}(\2)(\d){2}

private static void regexReference() {
        String date = "2020-06-20";
        String date1 = "2020/06/20";
        String date2 = "2020.06.20";
        String date3 = "2020-06.20";
        String regex = "(\\d){4}([\\-\\./])(\\d){2}([\\-\\./])(\\d){2}";
        System.out.println(Pattern.compile(regex).matcher(date).find());
        System.out.println(Pattern.compile(regex).matcher(date1).find());
        System.out.println(Pattern.compile(regex).matcher(date2).find());
        System.out.println(Pattern.compile(regex).matcher(date3).find());

        //匹配格式符号是分组2,所以引用的是分组2
        String regexReference = "(\\d){4}([\\-\\./])(\\d){2}(\\2)(\\d){2}";
        System.out.println(Pattern.compile(regexReference).matcher(date).find());
        System.out.println(Pattern.compile(regexReference).matcher(date1).find());
        System.out.println(Pattern.compile(regexReference).matcher(date2).find());
        System.out.println(Pattern.compile(regexReference).matcher(date3).find());

        //关于不引用分组,加了(?:)所以引用分组改成了引用分组1
        String regexReference1 = "(?:\\d){4}([\\-\\./])(\\d){2}(\\1)(\\d){2}";
        System.out.println(Pattern.compile(regexReference).matcher(date).find());
        System.out.println(Pattern.compile(regexReference).matcher(date1).find());
        System.out.println(Pattern.compile(regexReference).matcher(date2).find());
        System.out.println(Pattern.compile(regexReference).matcher(date3).find());
    }

反向引用分组

使用$数字来反向引用分组,反向引用分组是对结果的引用。
比如sql模糊查询的时候,输入的字符串中有%_需要进行其转义。在mysql中也是使用%进行转义。只有模糊查询的时候需要转义

String str = “%hel_”;
正则:[%_]
\就要用\\表示因为第二个反斜线在第二个参数中有特殊意义
str.replaceAll("([%])","\\\\$1")
输出为:**\%hel\
**

注意
分组后面有量词,所匹配的分组是最后一个满足的字符。
比如字符串:ab90。正则([ab90])。那么匹配的分组为0。
若想引用所用的加括号,正则改为(([ab90])
)。

正则中的贪婪匹配与惰性匹配

所谓贪婪量词即尽可能匹配多的字符满足正则表达式,所谓惰性量词即尽可能匹配少的字符满足正则表达式。
贪婪匹配的量词有:?,*,+,{m,n}
惰性匹配:在贪婪量词后加?即为惰性匹配。
惰性匹配的量词有:??,*?,+?,{m,n}?,还有分支结构**|**

    private static void greedAndInertia(){
        String str = "abcccde";
        String greedRegex = "abc+";
        Pattern greedPattern = Pattern.compile(greedRegex);
        Matcher greedMatcher = greedPattern.matcher(str);
        while (greedMatcher.find()) {
            System.out.println("贪婪匹配到的字符串为:");
            System.out.println(greedMatcher.group());//输出结果为:abccc
        }
        //惰性匹配
        String inertiaRegex = "abc+?";
        Pattern inertiaPattern = Pattern.compile(inertiaRegex);
        Matcher inertiaMatcher = inertiaPattern.matcher(str);
        while (inertiaMatcher.find()) {
            System.out.println("惰性匹配到的字符串为:");
            System.out.println(inertiaMatcher.group());//输出结果为:abc
        }
        }

注意: 分支结构"|"。

        String str = "foohefoot";
        String regex = "foo|foot";
        Pattern pattern = Pattern.compile(regex);
        Matcher matcher = pattern.matcher(str);
        while(matcher.find()){
            System.out.println(matcher.group());
        }

输出结果不是我们想要的foo和foot。两次的输出结果都是foo。
解决办法:

  • 自己写的正则,把匹配的长串放在前面。比如正则改成"foot|foo"
  • 接口传来的因其不可控,所以先用"|"进行分隔,转化成数组再转化成List。利用list.sort()方法。参数传一个Comparator匿名内部类,在compareTo方法中写比较的逻辑。或者直接使用Collections.sort(strList,Collections.reverseOrder());第二个参数代表逆序排序。String实现了Comparable,可以直接使用Collections.sort。但它是按照ascii值来排序的,但对于此问题没有影响。比如正则是foo|foot|g那么调用Collections.sort之后便是g,foot,foo满足foot在foo的前面

总结:无论贪婪匹配、惰性匹配都是在满足正则的基础上更多或更少的匹配字符。

正则匹配忽略大小写及区间去除

忽略大小写:(?i)
例如:(?i)abc。正则的含义为匹配abc且忽略大小写。
(?i)的作用范围在之后的所有字符(若没有括号)。若有括号作用范围就在括号内,包括子括号。
比如匹配abc其中忽略bc大小写。正则为:a(?i)bc
比如匹配abc其中忽略b大小写。正则为:a((?i)b)c
比如:a((?i)b(d)),代表的含义是匹配abd且bd忽略大小写。

区间内去除:[区间&&[^去除的字符]]
比如:匹配字符a-f但不包含ce。正则为:[a-f&&[^ce]]

匹配位置

什么是位置,也称为锚。字符相邻之间都有位置。比如字符串hello。箭头所示就是位置。
JAVA正则表达式进阶_第1张图片
hello的等价形式可写成。""即是位置。与图作为呼应。

“hello” == “” + “h” + “” + “e” + “” + “l” + “” + “l” + “o” + “”;

匹配位置的一共有八个:^、$、\b、\B、(?=p)、(?!p)、(?<=p)、((?。后四种叫做断言或是环视。在匹配的时候可以想象成等价的形式,看匹配的是哪个位置。

  • ^以什么开头。比如 ^abc则匹配的是以a开头的字符串后面是bc。匹配abc,abcd等,但不匹配babc。用在[^]中,表示不匹配的字符。比如[^bc]代表不匹配字符b跟c。
  • $以什么结尾。比如abc$。表示以c结尾的。比如addc,abcc,dabc等。
  • \b是单词边界,具体就是 \w 与 \W 之间的位置,也包括 \w 与 ^ 之间的位置,和 \w 与 $ 之间的位置。比如匹配单词feature。正则为\\dfeature\\d
    匹配的是单词边界,一个单词有两个边界。若正则写成feature\\d。则匹配的是以feature结尾的单词。
  • \B它的含义与\b相反。
  • (?=p) 肯定顺序环视。p是一个子模式,即该位置的后面匹配p。也可以理解成在模式p前面的位置。比如正则为(?=hel)。字符串为hello。匹配的即是hel前面的(位置),是图h前面的箭头是等价形式h前面的""。
  • (?!p)否定顺序环视。与肯定顺序环视相反。比如正则为(?!hel)。匹配的位置为除了hel前面的位置。即图中除了h前面的箭头其它位置都匹配。
  • (?<=p)肯定逆序环视。p是一个子模式,即该位置的前面匹配p。也可以理解成在模式p后面的位置。比如正则为(?=hel)。字符串为hello。匹配的即是hel后面的(位置),是图第一个l后面的箭头是等价形式第一个l后面的""。
  • ((?

比如 字符串"for example he is wise man"。想在这句话的后面加me too。是在后面的位置加。所以使用(?<=P)。正则为:(?<=he is wise man)

        String str = "for example he is wise man";
        String regex = "(?<=he is wise man)";
        System.out.println(str.replaceAll(regex," me too"));

环视匹配的是位置,替换或分隔时不会消除模式p的字符。

        String str = "hello";
        String regex = "l";
        System.out.println(Arrays.toString(str.split(regex)));//结果[he, , o]
        String lookAroundRegex = "(?=l)";
        System.out.println(Arrays.toString(str.split(lookAroundRegex)));//结果[he, l, lo]

小技巧:匹配不是开头位置 ,[?!^]

参考资料:
老马说编程
JavaScript正则表达式迷你书

每篇一语

拟把疏狂图一醉,对酒当歌,强乐还无味。——《蝶恋花·伫倚危楼风细细》柳永

你可能感兴趣的:(java)