day4 4 正则表达式详解及Demo

1.什么是正则表达式

正则表达式(Regular Expression)又称正规表示法、常规表示法,在代码中常简写为 regex、regexp 或 RE,它是计算机科学的一个概念。

正则表达式是一个强大的字符串处理工具,可以对字符串进行查找、提取、分割、替换等操作,是一种可以用于模式匹配和替换的规范。一个正则表达式就是由普通的字符(如字符 a~z)以及特殊字符(元字符)组成的文字模式,它用以描述在查找文字主体时待匹配的一个或多个字符串。

2.在java里如何使用正则表达式

String 类里也提供了如下几个特殊的方法。

boolean matches(String regex):判断该字符串是否匹配指定的正则表达式。
String replaceAll(String regex, String replacement):将该字符串中所有匹配 regex 的子串替换成 replacement。
String replaceFirst(String regex, String replacement):将该字符串中第一个匹配 regex 的子串替换成 replacement。
String[] split(String regex):以 regex 作为分隔符,把该字符串分割成多个子串。

matches的源码声明,可见是调用Pattern的matches方法。
在这里插入图片描述
上面这些特殊的方法都依赖于 Java 提供的正则表达式支持,除此之外,Java 还提供了 Pattern 和 Matcher 两个类专门用于提供正则表达式支持。

其实正则表达式是一种非常简单而且非常实用的工具。正则表达式是一个用于匹配字符串的模板。实际上,任意字符串都可以当成正则表达式使用。例如“abc”,它也是一个正则表达式,只是它只能匹配“abc”字符串。

2.1 如何创建正则表达式(规则)

1 合法字符

创建正则表达式就是创建一个特殊的字符串。正则表达式所支持的合法字符如表 1 所示。

在这里插入图片描述

2 特殊字符(限定符)

正则表达式中有一些特殊字符,这些特殊字符在正则表达式中有其特殊的用途,比如前面介绍的反斜线\\也是需要转义的)。也叫限定符。
正则表达式中允许使用限定修饰符来限定元字符出现的次数

如果需要匹配这些特殊字符,就必须首先将这些字符转义,也就是在前面添加一个反斜线\。正则表达式中的特殊字符如表 2 所示。

在这里插入图片描述
样式:
在这里插入图片描述
将上面多个字符拼起来,就可以创建一个正则表达式。例如:

"\u0041\\\\" // 匹配 A\
"\u0061\t"  // 匹配a<制表符>
"\\?\\["    // 匹配?[

注意:由上代码知正则表达式就是一字符串可能大家会觉得第一个正则表达式中怎么有那么多反斜杠?这是由于 Java 字符串中反斜杠本身需要转义,因此两个反斜杠(\\)实际上相当于一个(前一个用于转义)

3 预定义字符(元字符)

上面的正则表达式依然只能匹配单个字符,这是因为还未在正则表达式中使用“通配符”,“通配符”是可以匹配多个字符的特殊字符。正则表达式中的“通配符”远远超出了普通通配符的功能,它被称为预定义字符,正则表达式支持如表 3 所示的预定义字符。

元字符 正则表达式的写法 说明
. “.” 代表任意一个字符
\d “\\d” 代表 0~9 的任何一个数字
\D “\\D” 代表任何一个非数字字符
\s “\\s” 代表空白字符,如"\t’’和’’\n”
\S “\\S” 代表非空白字符
\W “\\W” 代表不可用于标识符的字符
\p {Lower} \\p {Lower} 代表小写字母 {a~z}
\p {Upper} \\p {Upper} 代表大写字母 {A~Z}
\p {ASCII} \\p {ASCII} ASCII 字符
\p {Alpha} \\p {Alpha} 字母字符
\p {Digit} \\p {Digit} 十进制数字,即 [0~9]
\p {Alnum} \\p {Alnum} 数字或字母字符
\p {Punct} \\p {Punct} 标点符号:!"#$%&’()*+,-./:;<=>?@[]^_`{
\p {Graph} \\p {Graph} 可见字符:[\p{Alnum}\p{Punct}]
\p {Print} \\p {Print} 可打印字符:[\p{Graph}\x20]
\p {Blank} \\p {Blank} 空格或制表符:[\t]
\p {Cntrl} \\p {Cntrl} 控制字符:[\x00-\x1F\x7F]

在正则表达式中,可以使用方括号括起来若干个字符来表示一个元字符。这个元字符可以代表方括号中的任何一个字符,例如字符串“reg=“a4”” “reg=“b4””和“reg=“c4””都是与“reg="[abc]4"”匹配的字符串。

最上面的 7 个预定义字符其实很容易记忆,其中:

  • d 是 digit 的意思,代表数字。
  • s 是 space 的意思,代表空白。
  • w 是 word 的意思,代表单词。
  • d、s、w 的大写形式恰好匹配与之相反的字符。

有了上面的预定义字符后,接下来就可以创建更强大的正则表达式了。例如:

c\\wt    // 可以匹配cat、cbt、cct、cOt、c9t等一批字符串,\\w即\w,表示匹配任何单词字符
\\d\\d\\d-\\d\\d\\d-\\d\\d\\d\\d    // 匹配如 000-000-0000 形式的电话号码

4 方括号表达式

在一些特殊情况下,例如,若只想匹配 a~f 的字母,或者匹配除 ab 之外的所有小写字母,或者匹配中文字符,上面这些预定义字符就无能为力了,此时就需要使用方括号表达式,方括号表达式有如表 4 所示的几种形式。

在这里插入图片描述
方括号表达式比前面的预定义字符灵活多了,几乎可以匹配任何字符。例如,若需要匹配所有的中文字符,就可以利用 [\u0041-\u0056] 形式——因为所有中文字符的 Unicode 值是连续的,只要找出所有中文字符中最小、最大的 Unicode 值,就可以利用上面形式来匹配所有的中文字符。

正则表达式还支持圆括号,用于将多个表达式组成一个子表达式,圆括号中可以使用或运算符|。例如,正则表达式“((public)|(protected)|(private))”用于匹配 Java 的三个访问控制符其中之一。

5 边界匹配符

除此之外,Java 正则表达式还支持如表 5 所示的几个边界匹配符

在这里插入图片描述

6 三种匹配模式

前面例子中需要建立一个匹配 000-000-0000 形式的电话号码时,使用了 \d\d\d-\d\d\d-\d\d\d\d 正则表达式,这看起来比较烦琐。实际上,正则表达式还提供了数量标识符,正则表达式支持的数量标识符有如下几种模式。

  1. Greedy(贪婪模式):数量表示符默认采用贪婪模式,除非另有表示。贪婪模式的表达式会一直匹配下去,直到无法匹配为止。如果你发现表达式匹配的结果与预期的不符,很有可能是因为你以为表达式只会匹配前面几个字符,而实际上它是贪婪模式,所以会一直匹配下去。
  2. Reluctant(勉强模式):用问号后缀(?)表示,它只会匹配最少的字符。也称为最小匹配模式。
  3. Possessive(占有模式):用加号后缀(+)表示,目前只有 Java 支持占有模式,通常比较少用。

三种模式的数量表示符如表 6 所示。

在这里插入图片描述
关于贪婪模式和勉强模式的对比,看如下代码:

String str = "hello,java!";
// 贪婪模式的正则表达式
System.out.println(str.replaceFirst("\\w*" , "■"));    //输出■,java!
// 勉强模式的正则表达式
System.out.println(str.replaceFirst("\\w*?" , "■""));    //输出■hello, java!

当从“hello java!”字符串中查找匹配\\w*子串时,因为\w*使用了贪婪模式,数量表示符*会一直匹配下去,所以该字符串前面的所有单词字符都被它匹配到,直到遇到空格,所以替换后的效果是“■,Java!”;如果使用勉强模式,数量表示符*会尽量匹配最少字符,即匹配 0 个字符,所以替换后的结果是“■hello,java!”。

3 Demo

3.1 简单演示匹配电话与邮箱

需求:用户输入电话与邮箱地址,判断其合法性。(电话是13或者15开头的11位的移动电话或者固定电话;邮箱仅支持qq邮箱及139邮箱)
代码演示:

package java基础;

import java.util.Scanner;

/**
 * @author Lh
 * @version  1.1.0
 * 需求:演示正则表达式匹配电话号码及邮箱
 */

public class regex_Demo {
    public static void main(String[]args){
        //匹配固定电话或者移动电话
        String regex_phone = "0\\d{2,3}[-]?\\d{7,8}|0\\d{2,3}\\s?\\d{7,8}|13[0-9]\\d{8}|15[1089]\\d{8}";
        //匹配邮箱,分名称,域名
        String regex_email = "^[a-zA-Z0-9_-]+@[a-zA-Z0-9_-]+(\\.[a-zA-Z0-9_-]+)+$";//^匹配开头,$匹配末尾
        Scanner sc = new Scanner(System.in);
        System.out.println("请输入电话:");
        String phone_number = sc.next();
        System.out.println("请输入邮箱:");
        String emil_number = sc.next();
        boolean result_p = phone_number.matches(regex_phone);
        boolean result_e = emil_number.matches(regex_email);

        if(result_p){
            System.out.println("电话格式匹配成功!");
        }
        else{
            System.out.println("电话格式匹配错误!");
        }
        if(result_e){
            System.out.println("邮箱格式匹配成功!");
        }
        else{
            System.out.println("邮箱格式匹配错误!");
        }

    }
}

在这里插入图片描述
在这里插入图片描述
正则表达式说明:
1.电话

电话号码包括固定电话和手机号码。其中固定电话是由区号和号码组成,区号是以 0 开头的,后面是 2~3 位数,因此在匹配区号的时候可以使用正则表达式0\d{2,3}。固定电话号码由 7~8 位数字组成,因此可以使用表达式\d{7,8}来进行匹配。固定电话的组合方式可能是“区号-号码”或者是“区号号码”,因此匹配固定电话号码时,可以使用“0\\d{2,3}[-]?\\d{7,8}|0\\d{2,3}\\s?\\d{7,8}”表达式。

手机号码是 11 位数,并且以数字 1 开头。考虑到手机号码的特殊性,这里使用“13[0-9]\\d{8}|15[1089]\\d{8}”表达式进行匹配。该正则表达式验证以 1315 开头的手机号码; 以 15 开头的电话号码,第 3 位数字只能是 1089 中的一个。

2.邮箱
只允许英文字母、数字、下划线、英文句号、以及中划线组成
例如:[email protected](名称+域名)
分析邮件名称部分:

  • 26个大小写英文字母表示为a-zA-Z
  • 数字表示为0-9
  • 下划线表示为_
  • 中划线表示为-
    由于名称是由若干个字母、数字、下划线和中划线组成,所以需要用到+表示多次出现
     根据以上条件得出邮件名称表达式:[a-zA-Z0-9_-]+

分析域名部分:

一般域名的规律为“[N级域名][三级域名.]二级域名.顶级域名”,比如“qq.com”、“www.qq.com”、“mp.weixin.qq.com”、“12-34.com.cn”,分析可得域名类似“** .** .** .**”组成。

  • “**”部分可以表示为[a-zA-Z0-9_-]+
  • “.**”部分可以表示为.[a-zA-Z0-9_-]+
  • 多个“.**”可以表示为(.[a-zA-Z0-9_-]+)+
     综上所述,域名部分可以表示为[a-zA-Z0-9_-]+(.[a-zA-Z0-9_-]+)+

最终表达式:
 由于邮箱的基本格式为“名称@域名”,需要使用“^”匹配邮箱的开始部分,用“$”匹配邮箱结束部分以保证邮箱前后不能有其他字符,所以最终邮箱的正则表达式为:

^[a-zA-Z0-9_-]+@[a-zA-Z0-9_-]+(\.[a-zA-Z0-9_-]+)+$  

关于邮箱的正则表达式的参考地址

4 Pattern类和Matcher类的使用

java.util.regex 是一个用正则表达式所订制的模式来对字符串进行匹配工作的类库包。它包括两个类:Pattern 和 Matcher。

Pattern 对象是正则表达式编译后在内存中的表示形式,因此,正则表达式字符串必须先被编译为 Pattern 对象,然后再利用该 Pattern 对象创建对应的 Matcher 对象。执行匹配所涉及的状态保留在 Matcher 对象中,多个 Matcher 对象可共享同一个 Pattern 对象。

因此,典型的调用顺序如下:

// 将一个字符串编译成 Pattern 对象,使用compile()
Pattern p = Pattern.compile("a*b");
// 使用 Pattern 对象创建 Matcher 对象
Matcher m = p.matcher("aaaaab");
boolean b = m.matches(); // 返回 true

上面定义的 Pattern 对象可以多次重复使用。如果某个正则表达式仅需一次使用,则可直接使用 Pattern 类的静态 matches() 方法,此方法自动把指定字符串编译成匿名的 Pattern 对象,并执行匹配,如下所示。

boolean b = Pattern.matches ("a*b","aaaaab");    // 返回 true

上面语句等效于前面的三条语句。但采用这种语句每次都需要重新编译新的 Pattern 对象,不能重复利用已编译的 Pattern 对象,所以效率不高。Pattern 是不可变类,可供多个并发线程安全使用。

Matcher 类提供了几个常用方法,如表 1 所示。
在这里插入图片描述
在 Pattern、Matcher 类的介绍中经常会看到一个 CharSequence 接口,该接口代表一个字符序列,其中 CharBuffer、String、StringBuffer、StringBuilder 都是它的实现类。简单地说,CharSequence 代表一个各种表示形式的字符串。

通过 Matcher 类的 find() 和 group() 方法可以从目标字符串中依次取出特定子串(匹配正则表达式的子串),例如互联网的网络爬虫,它们可以自动从网页中识别出所有的电话号码。下面程序示范了如何从大段的字符串中找出电话号码。

package java基础;

import java.util.regex.Matcher;
import java.util.regex.Pattern;//注意要导入类库

public class FindPhone {
    public static void main(String[] args) {
        // 使用字符串模拟从网络上得到的网页源码
        String str = "我想找一套适合自己的JAVA教程,尽快联系我13500006666" + "交朋友,电话号码是13611125565" + "出售二手电脑,联系方式15899903312";
        // 创建一个Pattern对象,并用它建立一个Matcher对象
        // 该正则表达式只抓取13X和15X段的手机号
        // 实际要抓取哪些电话号码,只要修改正则表达式即可
        Matcher m = Pattern.compile("((13\\d)|(15\\d))\\d{8}").matcher(str);
        // 将所有符合正则表达式的子串(电话号码)全部输出
        while (m.find()) {
            System.out.println(m.group());
        }
    }
}

运行结果:
在这里插入图片描述

从上面运行结果可以看出,find() 方法依次查找字符串中与 Pattern 匹配的子串,一旦找到对应的子串,下次调用 find() 方法时将接着向下查找。

提示:通过程序运行结果可以看出,使用正则表达式可以提取网页上的电话号码,也可以提取邮件地址等信息。如果程序再进一步,可以从网页上提取超链接信息,再根据超链接打开其他网页,然后在其他网页上重复这个过程就可以实现简单的网络爬虫了。

find() 方法还可以传入一个 int 类型的参数,带 int 参数的 find() 方法将从该 int 索引处向下搜索。start() 和 end() 方法主要用于确定子串在目标字符串中的位置,如下程序所示。

public class StartEnd {
    public static void main(String[] args) {
        // 创建一个Pattern对象,并用它建立一个Matcher对象
        String regStr = "Java is very easy!";
        System.out.println("目标字符串是:" + regStr);
        Matcher m = Pattern.compile("\\w+").matcher(regStr);
        while (m.find()) {
            System.out.println(m.group() + "子串的起始位置:" + m.start() + ",其结束位置:" + m.end());
        }
    }
}

上面程序使用 find()、group() 方法逐项取出目标字符串中与指定正则表达式匹配的子串,并使用 start()、end() 方法返回子串在目标字符串中的位置。运行上面程序,看到如下运行结果:

目标字符串是:Java is very easy!
Java子串的起始位置:0,其结束位置:4
is子串的起始位置:5,其结束位置:7
very子串的起始位置:8,其结束位置:12
easy子串的起始位置:13,其结束位置:17

matches() 和 lookingAt() 方法有点相似,只是 matches() 方法要求整个字符串和 Pattern 完全匹配时才返回 true,而 lookingAt() 只要字符串以 Pattern 开头就会返回 true。reset() 方法可将现有的 Matcher 对象应用于新的字符序列。看如下例子程序。

public class MatchesTest {
    public static void main(String[] args) {
        String[] mails = { "[email protected]", "[email protected]", "[email protected]", "[email protected]" };
        String mailRegEx = "\\w{3,20}@\\w+\\.(com|org|cn|net|gov)";
        Pattern mailPattern = Pattern.compile(mailRegEx);
        Matcher matcher = null;
        for (String mail : mails) {
            if (matcher == null) {
                matcher = mailPattern.matcher(mail);
            } else {
                matcher.reset(mail);
            }
            String result = mail + (matcher.matches() ? "是" : "不是") + "一个有效的邮件地址!";
            System.out.println(result);
        }
    }
}

上面程序创建了一个邮件地址的 Pattern,接着用这个 Pattern 与多个邮件地址进行匹配。当程序中的 Matcher 为 null 时,程序调用 matcher() 方法来创建一个 Matcher 对象,一旦 Matcher 对象被创建,程序就调用 Matcher 的 reset() 方法将该 Matcher 应用于新的字符序列。

从某个角度来看,Matcher 的 matches()、lookingAt() 和 String 类的 equals() 有点相似。区别是 String 类的 equals() 都是与字符串进行比较,而 Matcher 的 matches() 和 lookingAt() 则是与正则表达式进行匹配。

事实上,String 类里也提供了 matches() 方法,该方法返回该字符串是否匹配指定的正则表达式。例如:

"[email protected]".matches("\\w{3,20}@\\w+\\.(com|org|cn|net|gov)"); // 返回 true

除此之外,还可以利用正则表达式对目标字符串进行分割、查找、替换等操作,看如下例子程序。

public class ReplaceTest {
    public static void main(String[] args) {
        String[] msgs = { "Java has regular expressions in 1.4", "regular expressions now expressing in Java",
                "Java represses oracular expressions" };
        Pattern p = Pattern.compile("re\\w*");
        Matcher matcher = null;
        for (int i = 0; i < msgs.length; i++) {
            if (matcher == null) {
                matcher = p.matcher(msgs[i]);
            } else {
                matcher.reset(msgs[i]);
            }
            System.out.println(matcher.replaceAll("哈哈:)"));
        }
    }
}

上面程序使用了 Matcher 类提供的 replaceAll() 把字符串中所有与正则表达式匹配的子串替换成“哈哈:)”

实际上,String 类的replaceAll()、replaceFirst()、split() 等方法就是进行正则表达式匹配的。

正则表达式是一个功能非常灵活的文本处理工具,增加了正则表达式支持后的 Java,可以不再使用 StringTokenizer 类(也是一个处理字符串的工具,但功能远不如正则表达式强大)即可进行复杂的字符串处

發表評論
所有評論
還沒有人評論,想成為第一個評論的人麼? 請在上方評論欄輸入並且點擊發布.
相關文章