首页 > 技术文章 > 正则表达式基本内容及常用操作

zw971084570 2018-11-08 15:27 原文

在Java中的正则表达式,必须首先被编译为java.util.regex.Pattern的实例。然后,可将得到的模式用于创建Matcher对象,依照正则表达式,该对象可以与任意字符序列匹配。 执行匹配所涉及的所有状态都驻留在匹配器中,所以多个匹配器可以共享同一模式。
因此,典型的调用顺序是
    Pattern p=Pattern.compile("a*b");
    Matcher m=p.matcher("aaaaaab");
    boolean b=m.matches();
在仅使用一次正则表达式时,可以方便地通过此类定义mathces方法。此方法编译表达式并在单个调用中将输入序列与其匹配。语句
    boolean b=Pattern.matches("a*b","aaaaaab");
等效于上面的三个语句,尽管对于重复的匹配而言它效率不高,因为它不允许重用已编译的模式。
    1. 基本内容
      • 字符类
字符 说明
[abc] a,b或c中的某一个
[^abc] 非a,b或c中的任何一个
[a-zA-Z] a到z或A到Z,两头的字母包括在内
[a-d[m-p]] a到d与m到p(并集),等同于[a-dm-p]
[a-z&&[def]] d,e或f(交集)
[a-z&&[^bc]] a到z除了bc,等同于[ad-z](减去)
[a-z&&[^m-p]] a到z而非m到p(减去)
      • 预定义字符
字符 说明
. 任何字符
\d 数字
\D 非数字
\s 任何空白字符
\S 任何非空白字符
\w 单词字符(字母,数字,下划线)
\W 非单词字符,等同于[^\w]
      • 边界匹配器
字符 说明
^ 行的开始
$ 行的结束
\b 单词边界
\B 非单词边界
\A 输入开始
\Z 输入的结尾,仅用于最后一个结束符(如果有)
\z 输入的结尾
\G 上一个匹配的结尾
      • 数量词
字符 说明
X? 匹配X0到一次
X* 匹配X0到多次
X+ 匹配X一到多次
X{n} 匹配X恰好n次
X{n,} 匹配X至少n次
X{n,m} 匹配Xn到m次
      • 运算符
字符 说明
XY X后跟Y
X|Y X或Y
(X) X,作为捕获组
      • 引用
字符 说明
\n 任何匹配的捕获组
      • 反斜线,转义和引用

    反斜线(“\”)用于引用转义构造,同时还用于引用其他将被解释为非转义构造的字符。因此,表达式\\表示单个反斜线,而\{与左括号匹配。在不表示转义构造的任何字母字符前使用反斜线都是错误的;它们是为将来扩展正则表达式语言保留的。可以在非字母字符前使用反斜线,不管该字符是否非转义构造的一部分。

     根据Java Language Specification的要求,Java源代码的字符串中的反斜线被解释为Unicode转义或其它字符转义。因此必须在字符串字面值中使用两个反斜线,表示正则表达式受到保护,不被Java字节码编译器所编译。例如,当解释为正则表达式时,字符串字面值“\b”与单个退格字符匹配,而“\\b”与单词边界匹配。字符串字面值“\(hello\)”是非法的,将导致编译时错误,要与字符串(hello)匹配,必须使用字符串字面值“\\(hello\\)”;

      • 字符类

    字符类可以出现在其它字符类中,并且可以包含并集运算符和交集运算符。

    字符类运算符的优先级如下(由高到低):

  1. 字面值转义        \X
  2. 分组        [...]
  3. 范围        a-z
  4. 并集        [a-e][i-u]
  5. 交集        [a-z&&[aeiou]]
      • 组和捕获

    捕获组可以通过从左到右计算其开括号来编号。例如在表达式((A)(B(C)))中,存在四个这样的组:

  1. ((A)(B(C)))
  2. \A
  3. (B(C))
  4. (C)

组零始终代表整个表达式。
之所以这样命名捕获组是因为在匹配中,保存了与这些组匹配的输入序列的每个子序列。捕获的子序列稍后可以通过Back引用在表达式中使用,也可以在匹配操作完成后从匹配器获取。

    1. 正则表达式对字符串的常见操作
      • 匹配
                /*
                 *匹配手机号码
                */
                public static void function_demo1(){
                    String str="15839098373";
                    String regex="1[3578]\\d{9}";\\"\\d"可换成{0-9}
                    boolean b=str.matches(regex);
                    System.out.println(b);
                }
            
      • 切割
                切割实际上就是String的split方法,此方法接收一个正则表达式字符串作为参数
                /*
                 *切割以下字符串"a b c d e"
                */
                public static void function_demo2() {
                    String str="a b c d e";
                    String[] ch=str.split(" ");
                    for (String string : ch) {
                        System.out.println(string);
                    }
                }
                运行结果:
                a
                b
                c
                d
                e
                情境一:但是当字符串中间的空格数不确定时,如"a    b        c  d   e",此时再用split直接使用空格进行切割便得不到预想的结果;
                        此时,可用" +"正则表达式进行切割;即str.split(" +");
                情境二:当字符串为“a.b.c.d.e”时,此时再用“.”进行切割将得不到任何结果,因为“.”在正则表达式中代表任何字符,可使用"\\."进行转义;
                情境三:当字符串为“a###b@@@c$$d##e”或“attttttbmmmmcnnnn”时,该如何切割出abcde?先揭示一下正则表达式的写法:str.split("(.)\\1+");
                       解释一下"(.)\\1+":“.”在正则表达式中代码任意字符,我们想要去除的是字符串中重复的字符即字符出现的次数多于一次;“(.)”代表一
                个组,组中的“.”即任意字符,那么接下来就是要用正则表达式去让这个组能重复的出现。前面在讲捕获组的时候有说过每个字符串可以以左括号来区
                分组的编号,组零代表整个表达式即“(.)”;前面在讲引用的时候“\n”代表所引用的组,那么在这个示例中“\1”代表引用的是组1的编号中的内容
                即“.”;“+”代表出现一次或多次;综合起来这个正则表达式的意思就是连续出现一次或多次的字符;
            
      • 替换
                替换实际上用的是字符串的replaceAll方法,此方法接收两个参数,第一个参数为正则表达式,第二个参数为替换的字符串
                /*
                 *替换“attttbnnnnc”中连续出现的字符为e
                */
                public static void function_demo4() {
                    String str="attttbnnnnc";
                    String regex="(.)\\1+";
                    System.out.println(str.replaceAll(regex, "e"));
                }
                情境一:把上面字符串中连续出现的字符替换为一个,即替换为“atbnc”,str.replaceAll(regex,"$1")即可,“$1”代表拿前面被替换组中的字符
                情境二:把手机号码“15890875345”的中间四位用“*”隐藏掉
                public static void function_demo5() {
                    String string="15890987656";
                    String regex="(\\d{3})(\\d{4})(\\d{4})";
                    System.out.println(string.replaceAll(regex, "$1****$3"));
                }
                情境三:把字符串中的所有圆括号(中,英,全角,半角)替换为“*”
                public static void function_demo3() {
                    String str="a((((((((b(((((((c))))))))))))d()()0)e";
                    String regex="([\\(|(|\\)|)])";

                    System.out.println(str.replaceAll(regex, "*"));
                }
            
      • 获取
                因正则表达式会被编译为Pattern的实例,然后可用Pattern的matcher方法获取Matter对象,然后用Matter对象的实例方法进行相关的一些操作
                /*
                 *获取字符串中以三个字母组成的单词的字符串,“jin tian shi ge hao tian qi , xia le yi dian xiao yu”
                */
                public static void function_demo6() {
                    String str = "jin tian shi ge hao tian qi , xia le yi dian xiao yu";
                    String regex = "\\b[a-z]{3}\\b";// "\b"确定单词边界
                    Pattern p = Pattern.compile(regex);//获取编译的Pattern对象
                    Matcher m = p.matcher(str);//通过Pattern对象获取Matcher对象的实例
                    while (m.find()) {//查找与该模式匹配的输入序列的下一个子序列
                        System.out.println(m.group());//返回在以前匹配操作期间由给定组捕获的输入子序列
                    }
                }
            

练习

一,治疗口吃

        /*
         *“我我我我我我我我我......不不不不不不不不不不不不不不不......是是是是是......药药药药药......神神神神神......”
         */
        public static void function_exercise1(){
            String str="我我我我我我我我我......不不不不不不不不不不不不不不不......是是是是是......药药药药药......神神神神神......";
            str=str.replaceAll("\\.","");//先把字符串中的“.”去掉
            String regex="(.)\\1+";//把字符串中的重复字体去除的正则表达式
            System.out.println(str.replaceAll(regex,"$1"));
        }
    

二,对IP地址按照A类B类C类D类E类进行排序(不理解IP地址分类的可以去百度一下)

        /*
         *"192.168.0.1  89.123.0.1   3.3.3.3   156.0.231.5"对IP地址字符串进行排序
         *操作思路:IP地址是用点分十进制的形式由32位的二进制被划分成了四段,每段8位转成十进制而形成的,每段范围0-255
         *为了用TreeSet对对象进行排序,所以需要对每个IP地址的每一段进行等位补充,比如地址中的某一段是一位数的补为三位
         *数,然后放到TreeSeet中进行排序
         */
        public static void function_demo7() {
            String ip_str="192.168.0.1  89.123.0.1   3.3.3.3   156.0.231.5";
            String[] ip_strArry=ip_str.split(" +");//把IP地址用空格打散成一个字符数组
            TreeSet ts=new TreeSet<>();//用TreeSet对对象进行排序
            System.out.println("排序前:");
            for(String str:ip_strArry) {
                System.out.println(str);
                str=str.replaceAll("(\\d+)", "00$1");//把每一段的数值前补充两个零,此时192.168.0.1变成了00192.00168.000.001
                                                     //而3.3.3.3变成了003.003.003.003
                str=str.replaceAll("0*(\\d{3})", "$1");//把IP地址变为五位的,如00192,前两位的零去除,此时每个IP地址的每一段都是三位数
                ts.add(str);//放入TreeSet中,默认排序
            }
            System.out.println("------------------");
            System.out.println("排序后:");
            for(String str:ts) {
            str=str.replaceAll("0*(\\d+)", "$1");//把003这样的段位的前两个零去除
            System.out.println(str);
            }
        }
    

三,网络爬虫

        /*
         *爬取网页上面的邮箱号
         */
        public static void function_demo8() throws IOException {
            URL url=new URL("http://localhost:63342/BoKeYuanComplieProject/regex.html");//需要抓取的网页地址
            //BufferedReader br = new BufferedReader(new FileReader("C:\\regex.html"));//抓取的本地文件
            BufferedReader br = new BufferedReader(new InputStreamReader(url.openStream()));
            String regex="\\w+@\\w+\\.([a-zA-Z]{1,3}){1,2}";//邮箱
            Pattern p=Pattern.compile(regex);//把正则编译为Pattern对象
            List list=new ArrayList();//存放抓取后的邮箱信息
            String line = null;
            while ((line = br.readLine()) != null) {
                Matcher m=p.matcher(line);
                if(m.find()) {
                    list.add(m.group());
                }
            }
            for(String str:list) {
                System.out.println(str);
            }
        }
    

推荐阅读