正则表达式的总结

正则表达式学习

  • 正则表达式就用来是解决字符串搜索与替换问题的,能够用正则表达式引擎判断一个字符串是否是符合正则表达式引擎中的定义好的规则,而且规则每每是几个简单地字符表示的,看上去很简洁,只不过若是不会正则表达式的,看起来会更加难懂
  • 正则表达式的做用核心是使用状态机的思想进行模式匹配
  • 我的理解写好正则表达式只须要组合好 惟一匹配+重复匹配+规则关系便可

字符

  • 精确匹配,也就是如出一辙java

    System.out.println("t".matches("t"));  // true
    System.out.println("t*".matches("t\\*"));  // true 注意对于通常字符串 *不是特殊字符 在正则中*是特殊字符,所以要表达其原本的意思须要使用转义(此转义时正则中的转义,不是通常的转义字符),即\*,可是\*并不是标准的转义字符,所以使用\字符与*字符拼接标识转义(此转义时正则中的转义,而不是通常字符串中的转义),而表达字符则须要\\
    System.out.println("$".matches("\\$"));  // true
    
    System.out.println("\t".matches("\t"));  // false 正则匹配转义字符,源字符串解析不解析为转义
    System.out.println("\t".matches("\\t")); // true正则从左到右匹配,在正则中用到了对于\的转义标识\字符串自己,这里匹配时把普通字符串中的转义字符视为两个字符,至关于精确匹配
    System.out.println("\\t".matches("\\t")); // false 
    System.out.println("\\t".matches("\\\\t")); // true 正则表达式中匹配转义,源字符串不匹配转义
    复制代码
    • 对于正常的字符串来讲,除了普通字符和转义字符以外,特殊字符有以下几个git

      • \ 做为转义字符的标识符,若是想不作为标识符使用,而是单独做为字符串自己使用,必须使用\\github

      • ' 单引号,Java的字符,js的字符串,均可以使用单引号标识,所以为了不混淆使用\'标识单引号自己正则表达式

      • "双引号,多种语言的字符都使用双引号标识,为了不混淆使用\"标识双引号字符自己markdown

    • 正则接受通常的转义字符,也接受正则本身引入的所谓的转义字符(提供正则功能)oop

      • 对于正则引擎来讲对于通常的转义字符,\与后边的被转译字符视为一个字符(事实上也确实如此),而对于正则本身引入的转义字符,\与后边的被转译字符视为两个字符,只不过这种字符组合构成了转义的功能
        • 可是对于待匹配或替换处理的原始字符串不会把转义字符当作一个字符来处理,而是拆开处理
      • 正则本身引入的字符
        • 让特殊字符失去其本意的转义 System.out.println("t*".matches("t\\*"));
        • 提供特殊功能的转义 \w \d
  • 使用正则表达式引擎提供的特殊字符时务必注意不要与转义字符混淆post

    • 转义字符的做用是让一个特殊字符失去其自己的含义,而做为一个普通的字符,对于普通字符来讲即便使其具备特殊的含义性能

      • 特殊字符以及其自己含有的特殊含义包括:学习

        • 表示惟一匹配(匹配有限数量的字符)网站

          • . 匹配一个而且必须是1个的除了换行符以外的任意字符

          • \d 匹配一个数字

          • \D除了数字以外的字符

          • \w字母、数字、下划线

          • \W除了字母、数字、下划线以外的字符

          • \s空白字符(空格+制表符+换页符+换行符)

          • \S除了空白字符以外的其余字符

          • 指定范围的匹配

            • [123] 枚举匹配
              • 枚举匹配时须要注意,方括号内能够直接枚举特殊字符,包括 ?+ 等等而不须要作转义便可表示字符串自己*
            • [n-m1-2A-b]指定范围匹配(范围的起始点与终止点都包含)(注意多范围之间无空格)
              • 使用^取非 注意仅在范围匹配,也就是方括号内部使用^作取非
          • 除了上述的特殊字符外,正则还提供了一些其余的特殊字符,部分特殊字符与普通的转义字符重复

            image-20210213152433563
        • 重复匹配(匹配无限数量的字符)(通配符+集合区间)(重复匹配做为修饰符放在惟一匹配的字符后边)

          • 星号 匹配任意多个符合规则的字符
          • 加号 匹配至少一个
          • ? 匹配0个或者1个
          • {n} 匹配n个
          • {n,m}匹配至少n个,至多m个,m省略表示至少含义,n设置为0表示至多含义
      • 对于普通字符的转义

        • image-20210212234927819
      • 正则表达式中须要进行转义以表示字符自己的特殊字符

      • .

      • ?

      • $

      • ^

      • 方括号

      • 圆括号

      • 大括号

      • |

      • \

规则

位置边界+正则模式

  • 位置边界用来限制匹配的位置,经过限制匹配的位置筛选掉不符合条件的匹配

单词边界

  • \b字符边界(注意这个正则的特殊字符与普通转义字符中有重复,在正则中显然做为字符边界处理)

    • b是boundary的首字母。在正则引擎里它匹配的是能构成单词的字符(\w)和不能构成单词的字符(\W)中间的那个位置
  • \B标识非字符边界

  • 案例

    String p = "The cat scattered his food all over the room.";
    System.out.println(p.replaceAll("\\bcat\\b", "*"));  // The * scattered his food all over the room.
    复制代码
    • 注意正则的内置特殊字符的使用方式
    • 注意replaceAll API的使用:直接返回更改后的新的String,而不是把新的String赋给原字符串变量引用变量

字符串边界

  • 适用于多行匹配的模式下对于字符串的匹配

    • ^标识字符串的开头

      • 是否使用^边界符号的区别在于,是不是针对整个字符串的匹配

        "(T|t)he" => The car is parked in the garage.    // 字符串中的两个the都会被选中
        "^(T|t)he" => The car is parked in the garage.   // 只有字符串首部的The被选中,由于筛选的是以The或者the开头的字符串
        复制代码
    • $标识字符串的结尾

      • 一样的,是否使用$边界符号的区别在于,是不是针对整个字符串的匹配

        "(at\.)" => The fat cat. sat. on the mat.      // cat. sat. mat.都被选中
        "(at\.)$" => The fat cat. sat. on the mat.     // 只有最后一个mat.被选中
        复制代码
  • 字符串边界符号与正则模式的配合使用

    • 在多行字符串的匹配中当只是用字符串边界符号时,仅仅能匹配第一行或最后一行,由于有换行符的存在因此只能匹配一行

      String p1 = String.join("\n", "I am scq000.", "I am scq000.");
      // *
      // I am scq000.
      System.out.println(p1.replaceAll("^I am scq000\\.", "*"));
      
      
      // I am scq000.
      // *
      System.out.println(p1.replaceAll("I am scq000\\.$", "*"));
      
      // 多行没法匹配
      // I am scq000.
      // I am scq000.
      System.out.println(p1.replaceAll("^I am scq000\\.$", "*"));
      复制代码
    • 多行匹配的引入

      System.out.println(p1.replaceAll("(?m)^I am scq000\\.$", "*"));
      复制代码
      • 使用(?m)引入多行模式
      Pattern p = Pattern.compile("^I am scq000\\.$", Pattern.MULTILINE);
      Matcher m = p.matcher(p1);
      System.out.println(m.find());
      复制代码
      • 使用Pattern解析正则,引入正则模式
      Pattern p1 = Pattern.compile("^.*b.*$");
      //输出false,由于正则表达式中出现了^或$,默认只会匹配第一行,第二行的b匹配不到。
      System.out.println(p1.matcher("a\nb").find());
      Pattern p2 = Pattern.compile("^.*b.*$",Pattern.MULTILINE);
      //输出true,指定了Pattern.MULTILINE模式,就能够匹配多行了。
      System.out.println(p2.matcher("a\nb").find());
      复制代码
      "/.at(.)?$/" => The fat
                      cat sat
                      on the mat.    只有mat匹配
        
      "/.at(.)?$/gm" => The fat
                      cat sat
                      on the mat.   fat sat mat都匹配
      复制代码

正则模式

  • m 多行模式

  • g 全局模式 匹配所有的符合规则的而不仅是第一个

  • i忽略大小写

  • 指定模式的两种方式:

    • 在正则表达式中指定

      • 注意Java中使用正则表达式模式的方式
      String p1 = String.join("\n", "I Am scq000.", "I am scq000.");
      // 结果为:
      // I Am scq000.
      // * 
      // 可见第一行并未匹配
      System.out.println(p1.replaceAll("I am scq000\\.", "*"));
      
      // 多行模式 大小写无关模式
      // 结果为:
      // *
      // * 
      // 可见所有都匹配
      System.out.println(p1.replaceAll("(?mi)I am scq000\\.", "*"));
      复制代码
      • 在最开头使用(?m) (?i)来标识正则模式,也可使用组合模式(?mi)
      • 全局模式不能使用此方法指定,全局仍是非全局更多的是经过API的方式,好比replaceAll replaceFirst
    • 在API参数中指定

      Pattern p1 = Pattern.compile("^.*b.*$");
      //输出false,由于正则表达式中出现了^或$,默认只会匹配第一行,第二行的b匹配不到。
      System.out.println(p1.matcher("a\nb").find());
      Pattern p2 = Pattern.compile("^.*b.*$",Pattern.MULTILINE);
      //输出true,指定了Pattern.MULTILINE模式,就能够匹配多行了。
      System.out.println(p2.matcher("a\nb").find());
      复制代码
      • Pattern解析正则时除了提供了多行模式外,还提供了如下几种模式

        • DOTALL模式 用来解决正则表达式中的.通配符不包含换行符带来的问题

          Pattern p1 = Pattern.compile("a.*b");
          //输出false,默认点(.)没有匹配换行符
          System.out.println(p1.matcher("a\nb").find());
          Pattern p2 = Pattern.compile("a.*b", Pattern.DOTALL);
          //输出true,指定Pattern.DOTALL模式,能够匹配换行符。
          System.out.println(p2.matcher("a\nb").find());
          复制代码
        • UNIX_LINES

        • CASE_INSENSITIVE

        • LITERAL

        • UNICODE_CASE

        • CANON_EQ

        • UNICODE_CHARACTER_CLASS

        • 同时使用多个模式的案例

          Pattern p1 = Pattern.compile("^a.*b$");
          //输出false
          System.out.println(p1.matcher("cc\na\nb").find());
          Pattern p2 = Pattern.compile("^a.*b$", Pattern.DOTALL);
          //输出false,由于有^或&没有匹配到下一行
          System.out.println(p2.matcher("cc\na\nb").find());
          Pattern p3 = Pattern.compile("^a.*b$", Pattern.MULTILINE);
          //输出false,匹配到下一行,但.没有匹配换行符
          System.out.println(p3.matcher("cc\na\nb").find());
          //指定多个模式,中间用|隔开
          Pattern p4 = Pattern.compile("^a.*b$", Pattern.DOTALL|Pattern.MULTILINE);
          //输出true
          System.out.println(p4.matcher("cc\na\nb").find());
          复制代码
      • 以上各个模式的用途可查看源码注释,每个模式都支持对应的正则表达式内嵌的标识方法,能够参考其注释

子表达式

  • 使用小括号将表达式进行拆分,获得子正则表达的组合,更加灵活。
  • 一个简单的例子:座机号码的区号-电话号的组合,匹配以后,想拆分出区号与电话号,可使用split,substring等复杂的方法,可是比较麻烦,而且没有复用性,使用分组能够方便的进行拆分并获得其匹配的值
  • 要想高效使用分组子表达式须要用到回溯引用
回溯引用
  • 回溯引用是在分组的基础之上使用的,指的是模式的后边的部分引用前边部分的已经匹配了的子表达式,使用回溯表达式有如下两点好处

    • 能够写出更加精简高效的正则

      • 在正则表达式中直接使用回溯的语法是:
        • 回溯引用的语法像\1,\2,....,其中\1表示引用的第一个子表达式,\2表示引用的第二个子表达式,以此类推。而\0则表示整个表达式
        • 案例:匹配字符串中的两个连续的相同的单词 Hello what what is the first thing, and I am am scq000.---\b(\w+)\s\1
    • 可使用正则抽取分组信息

      • Pattern类配合Matchr类

        String regex20 = "([0-9]{3,4})-([1-9]{7,8})";
        Pattern pattern = Pattern.compile(regex20);
        Matcher matcher = pattern.matcher("0312-2686815");
        
        // 注意只有通过matches判断后的matcher才能进行分组提取,不然会报错No Match Fund
        if (matcher.matches()) {
          // 注意分组从1开始,序号为0的分组是字符串总体
          // 区号
          System.out.println(matcher.group(1));
          // 电话号
          System.out.println(matcher.group(2));
          // 匹配的总体
          System.out.println(matcher.group(0));
        } else {
          System.out.println("不匹配");
        }
        
        
        // 去掉区号
        System.out.println("0312-2686815".replaceAll(regex20, "$2"));
        复制代码
        • "str.matches"方法内部使用的也是Pattern与Matchr,每一次调用方法都建立一个新的Patterm对象和一个新的Matcher对象,推荐直接定义一个Pattern来复用,以提高性能
        • 使用回溯进行分组提取时,使用的特殊字符为$1而不是\1,一样注意$0表明总体,$1才是第一个分组,以此类推
    • 若是要拒绝子正则表达式被引用,则在子正则的前边加上?:

      String regex20 = "(?:[0-9]{3,4})-([1-9]{7,8})";
      // $0仍然是表示总体,$1由第二个子表达式补位,此时再引用$2 会报错No Group 2
      System.out.println("0312-2686815".replaceAll(regex20, "$2"));
      复制代码
    • 由上边的非捕获正则?:引出前向查找和后向查找(也能够称做零宽度断言)----相对来讲说比较难以理解

      • 使用断言的说法更容易理解,由于其做用就在于对子表达式附加断言,从而为正则匹配添加筛选条件,所谓先后就是表征这个断言限制条件是做用在子表达式前仍是后(关于后发的写法的记忆手段:从后指向前的箭头),所谓正负表征的就是逻辑上的是与否(正负在写法上的差异就是=与!的差异)
      • 须要注意的是断言表达式自己不做为匹配的内容,只是做为断言的辅助说明
      • 正先行断言 (?=regex)
        • "(T|t)he(?=\sfat)" => The fat cat sat on the mat. 匹配第一个The
        • 除断言以外的表达式能够匹配两个the,加入断言后只匹配第一个The,由于其后边是 fat
      • 负先行断言 (?!regex)
        • "(T|t)he(?!\sfat)" => The fat cat sat on the mat. 匹配第二个the
        • 筛选出不知足断言的
      • 正后发断言 (?<=regex)
        • "(?<=(T|t)he\s)(fat|mat)" => The fat cat sat on the mat. 匹配fat和mat
        • 断言条件的位置在子正则以前
      • 负后发断言 (?<!regex)
        • "(?<!(T|t)he\s)(cat)" => The cat sat on cat. 匹配第二个cat

逻辑组合

  • 在正则中,多个规则写在一块儿时,默认是与关系
  • |或关系
  • [^] 枚举或指定范围的匹配的取反
  • ! 负先发断言,负后发断言

贪婪匹配与惰性匹配

  • 默认是贪婪匹配,贪婪匹配会在从左到右的匹配中匹配尽量多的字符 "/(.*at)/" => The fat cat sat on the mat. 整个字符串所有匹配
  • 贪婪匹配更改成惰性匹配使用?便可:将?加在重复匹配的修饰符(* + ?等)后边便可
  • 案例
    • "/(.*?at)/" => The fat cat sat on the mat. at前边是任意数量的字符,惰性匹配就在这个任意数量的修饰符后加上?标识尽量少匹配字符 所以只匹配The fat
    • 判断字符串数字末尾的0的个数
      • 123000 -> "(\\d+)(0*)" -> "123000" "" 至少一个数字?我全都要
      • 123000 -> "(\\d+?)(0*)" -> "123" "000" 至少一个数字?那就给你1个吧!可是考虑到后边的只想要0,那就把0前边的都给你
      • 0000 -> "(\\d+?)(0*)" -> "0" "000" 至少一个数字,那就给你1个,其他的后边的正好都要!哈哈
      • 9999 -> "(\\d??)(9*)" -> "" "9999" 无关紧要,正好后边都想要呢,那你就没了~

参考

相关文章
相关标签/搜索