基本介绍

正则表达式就是用某种模式去匹配字符串的一个公式,许多语言都支持正则表达式。

  1. public static void main(String[] args) {
  2. String content = "1895 iajdijada dsjnda sjidjw 1111";
  3. //1. \\d 表示一个任意的数字,因此需要找到四个数字的组合
  4. String regStr = "\\d\\d\\d\\d";
  5. //2. 创建模式对象(即正则表达式对象)
  6. Pattern pattern = Pattern.compile(regStr);
  7. //3. 创建匹配器,按照正则表达式的规则去匹配 content字符串
  8. Matcher matcher = pattern.matcher(content);
  9. //开始匹配
  10. while(matcher.find()){
  11. System.out.println("找到:" + matcher.group(0));
  12. }
  13. }

image.png

底层逻辑解析

matcher.find()

  1. 根据指定的规则,定位满足规则的子字符串(比如1895)
  2. 找到后,将子字符串开始的索引和结束的索引+1 记录到 matcher对象的属性int[] groups。即groups[0] = 0,把该子字符串的结束的索引+1的值记录到 groups[1] = 4
  3. 同时记录属性 oldLast 的值为子字符串的结束的索引+1的值即4,下次执行find时,就从4开始匹配

    group方法

    1. public String group(int group) {
    2. if (first < 0)
    3. throw new IllegalStateException("No match found");
    4. if (group < 0 || group > groupCount())
    5. throw new IndexOutOfBoundsException("No group " + group);
    6. if ((groups[group*2] == -1) || (groups[group*2+1] == -1))
    7. return null;
    8. return getSubSequence(groups[group * 2], groups[group * 2 + 1]).toString();
    9. }

    根据源码可以看出,最后matcher.group(0) 的返回值是从 content 开始截取字符串[0,4),即从索引为0开始,一直截取到索引为3的地方,结果为1895。
    再次调用find方法时,继续从索引为4开始寻找,如果找到的话,会把groups[0]和groups[1]覆盖掉 (29 和 33)。因此自始至终都是输出 matcher.group(0)。

    考虑分组

    1. String regStr = "(\\d\\d)(\\d\\d)"

    正则表达式有() 表示分组,第一个()表示第一组,第2个()表示第2组…
    当找到满足条件的字串时(比如1895),还是将索引记录到 int[] groups,其中 groups[0] = 0,groups[1] = 4,这是不变的。
    但是因为有了分组,因此我们需要把每一组对应的开始和结束+1都记录在groups中。第一组:groups[2] = 0,groups[3] = 2,表示从[0,2)的字串。 第二组:groups[4] = 2,groups[5] = 4。
    下面的步骤就一样了。查看分组内容依然使用 group方法。
    image.png

    1. public static void main(String[] args) {
    2. String content = "1895 iajdijada dsjnda sjidjw 1111";
    3. //1. \\d 表示一个任意的数字
    4. String regStr = "(\\d\\d)(\\d\\d)";
    5. //2. 创建模式对象(即正则表达式对象)
    6. Pattern pattern = Pattern.compile(regStr);
    7. //3. 创建匹配器,按照正则表达式的规则去匹配 content字符串
    8. Matcher matcher = pattern.matcher(content);
    9. //开始匹配
    10. while(matcher.find()){
    11. System.out.println("找到:" + matcher.group(0));
    12. //查看分组内容依然使用 group方法
    13. System.out.println("第一组:" + matcher.group(1));
    14. System.out.println("第二组:" + matcher.group(2));
    15. }
    16. }

    image.png

    正则表达式语法

    字符匹配符

    image.png
    注:[0-9]表示可以匹配0-9之间的任意字符,[a-z]表示可以匹配任意小写字母。[^0-9]表示可以匹配不是0-9的任意一个字符。

    1. public static void main(String[] args) {
    2. String content = "1895 iajdijada dsjnda sjidjw 1111";
    3. //String regStr = "[0-9]";
    4. String regStr = "[abcd]";
    5. Pattern pattern = Pattern.compile(regStr);
    6. Matcher matcher = pattern.matcher(content);
    7. while (matcher.find()) {
    8. System.out.println("找到:" + matcher.group(0));
    9. }
    10. }

    image.png
    image.png
    image.png
    注:Java正则表达式默认是区分字母大小写的,如果需要实现不区分大小写,那么需要用到(?i)

  • (?i)abc 表示abc都不区分大小写
  • a(?i)bc 表示bc不区分大小写
  • a((?i)b)c 表示只有b不区分大小写

也可以使用 Pattern.compile(regEx,Pattern.CASE_INSENSITIVE) 表示该正则表达式不区分大小写。

  1. public static void main(String[] args) {
  2. String content = "abcabcabc";
  3. // String regStr = "abc";
  4. String regStr = "(?i)ABC";
  5. Pattern pattern = Pattern.compile(regStr);
  6. Matcher matcher = pattern.matcher(content);
  7. while (matcher.find()) {
  8. System.out.print("找到:" + matcher.group(0) + " ");
  9. }
  10. }

image.png
image.png

选择匹配符

在匹配某个字符串的时候是选择性的,即:既可以匹配这个,又可以匹配那个,这时需要用到选择匹配符号。
image.png

  1. public static void main(String[] args) {
  2. String content = "aqqbsssc";
  3. String regStr = "a|b|c";
  4. Pattern pattern = Pattern.compile(regStr);
  5. Matcher matcher = pattern.matcher(content);
  6. while (matcher.find()) {
  7. System.out.print("找到:" + matcher.group(0) + " ");
  8. }
  9. }

image.png

限定符

用于指定其前面的字符和组合项连续出现多少次。
image.png
image.png
所有的符号都是匹配前一个字符,有括号就匹配括号里的,没有就是前一个字符。

  1. public static void main(String[] args) {
  2. String content = "123456789";
  3. //String regStr = "\\d{2}";
  4. //String regStr = "\\d{2,}"; //匹配两位以上的数字
  5. String regStr = "\\d{2,5}"; //匹配2,3,4,5位数字
  6. Pattern pattern = Pattern.compile(regStr);
  7. Matcher matcher = pattern.matcher(content);
  8. while (matcher.find()) {
  9. System.out.print("找到:" + matcher.group(0) + " ");
  10. }
  11. }

image.pngimage.png
image.png
注:匹配中默认为贪婪匹配原则,即尽可能匹配较大的字符串。比如 \d{2,5},会先去寻找5位满足条件的字符串,然后是四位…最后才是两位。

  1. public static void main(String[] args) {
  2. String content = "11111111";
  3. String regStr = "1{3,4}";
  4. Pattern pattern = Pattern.compile(regStr);
  5. Matcher matcher = pattern.matcher(content);
  6. while (matcher.find()) {
  7. System.out.print("找到:" + matcher.group(0) + " ");
  8. }
  9. }

默认先匹配四位,即1111。
image.png

  1. public static void main(String[] args) {
  2. String content = "aaa11111111";
  3. //String regStr = "a1+"; 匹配 1~∞个1
  4. //String regStr = "a1*"; 匹配 0~∞个1
  5. String regStr = "a1?"; //匹配0~1 个1
  6. Pattern pattern = Pattern.compile(regStr);
  7. Matcher matcher = pattern.matcher(content);
  8. while (matcher.find()) {
  9. System.out.print("找到:" + matcher.group(0) + " ");
  10. }
  11. }

image.png
image.png
image.png

定位符

定位符,规定要匹配的字符串出现的位置,比如在字符串的开始还是在结束的位置。
image.png
注:1. 除了起始字符匹配的是后面,其他匹配的还是前面。2. 起始和结束指的是整个字符串,而不是截取出的字串。

  1. public static void main(String[] args) {
  2. String content = "123aa99cccc";
  3. //+修饰[0-9],即必须以至少一个数字开头,后接任意个小写字母
  4. String regStr = "^[0-9]+[a-z]*";
  5. Pattern pattern = Pattern.compile(regStr);
  6. Matcher matcher = pattern.matcher(content);
  7. while (matcher.find()) {
  8. System.out.print("找到:" + matcher.group(0) + " ");
  9. }
  10. }

image.png
为什么没有 “99cccc”:因为要求的是整个字符串必须以至少一个数字开头,字串以数字开头是不可以的。

分组

image.png
非命名捕获其实就是正常的分组,使用 matcher.group(n) 来输出对应分组的内容。

  1. public static void main(String[] args) {
  2. String content = "1895 iajdijada dsjnda sjidjw 1111";
  3. //1. \\d 表示一个任意的数字
  4. String regStr = "(\\d\\d)(\\d\\d)";
  5. //2. 创建模式对象(即正则表达式对象)
  6. Pattern pattern = Pattern.compile(regStr);
  7. //3. 创建匹配器,按照正则表达式的规则去匹配 content字符串
  8. Matcher matcher = pattern.matcher(content);
  9. //开始匹配
  10. while(matcher.find()){
  11. System.out.println("找到:" + matcher.group(0));
  12. //查看分组内容依然使用 group方法
  13. System.out.println("第一组:" + matcher.group(1));
  14. System.out.println("第二组:" + matcher.group(2));
  15. }
  16. }

命名分组:可以给分组取名,然后根据 matcher.group(name) 输出分组对应的内容。

  1. public static void main(String[] args) {
  2. String content = "1998absdua2022";
  3. //+修饰[0-9],即必须以至少一个数字开头,后接任意个小写字母
  4. String regStr = "(?<g1>\\d\\d)(?<g2>\\d\\d)";
  5. Pattern pattern = Pattern.compile(regStr);
  6. Matcher matcher = pattern.matcher(content);
  7. while (matcher.find()) {
  8. System.out.println("找到:" + matcher.group(0));
  9. System.out.println("第一个分组内容:" + matcher.group(1));
  10. System.out.println("第一个分组内容:" + matcher.group("g1"));
  11. System.out.println("第二个分组内容:" + matcher.group(2));
  12. System.out.println("第二个分组内容:" + matcher.group("g2"));
  13. }
  14. }

image.png
还有一些其他特殊的分组:
image.png

  1. public static void main(String[] args) {
  2. String content = "helloMerlin校长 Merlin老师 helloMerlin同学";
  3. //等同于 "Merlin校长|Merlin老师|Merlin同学"
  4. String regStr = "Merlin(?:校长|老师|同学)";
  5. Pattern pattern = Pattern.compile(regStr);
  6. Matcher matcher = pattern.matcher(content);
  7. while(matcher.find()){
  8. System.out.println("找到:" + matcher.group(0));
  9. }
  10. }

image.png

  1. public static void main(String[] args) {
  2. String content = "helloMerlin校长 Merlin老师 helloMerlin同学";
  3. //String regStr = "Merlin(?=校长|老师)";
  4. String regStr = "Merlin(?!校长|老师)";
  5. Pattern pattern = Pattern.compile(regStr);
  6. Matcher matcher = pattern.matcher(content);
  7. while(matcher.find()){
  8. System.out.println("找到:" + matcher.group(0));
  9. }
  10. }

(?=pattern):找到Merlin关键字,但是只能匹配后面带有 “校长” 和 “老师” 的Merlin。
image.png
(?!pattern):和上面正好反过来,它是不能匹配后面带有 “校长” 和 “老师” 的Merlin。
image.png

反向引用

圆括号的内容被捕获后,可以在这个括号后被使用,从而写出一个比较实用的匹配模式,这个我们称为反向引用,这种引用既可以是在正则表达式内部,也可以是在正则表达式外部,内部反向引用:\分组号,外部反向引用:$分组号。
比如:要匹配两个连续的相同的数字:(\d)\1。要匹配五个连续的相同数字:(\d)\1{4}
要匹配个位与千位相同,十位与百位相同的数(如5225,1551等):(\d)(\d)\2\1 ( \2表示第二个分组,即百位。\1表示第一个分组,即千位)

  1. public static void main(String[] args) {
  2. //满足前面是一个五位数,然后是一个-号,然后是一个九位数,三三相同(如12345-333999111)
  3. String content = "12345-333999111";
  4. String regStr = "\\d{5}-(\\d)\\1{2}(\\d)\\2{2}(\\d)\\3{2}";
  5. Pattern pattern = Pattern.compile(regStr);
  6. Matcher matcher = pattern.matcher(content);
  7. String newContent = matcher.replaceAll("你好");
  8. System.out.println(newContent);
  9. while(matcher.find()){
  10. System.out.println("找到:" + matcher.group(0));
  11. }
  12. }

非贪婪匹配

如果要实现非贪婪匹配,可以在对应表达式最后加上一个 ‘?’ 。

  1. public static void main(String[] args) {
  2. String content = "111111";
  3. String regStr = "\\d+?"; //非贪婪匹配,这次只匹配1个 (1~∞)
  4. //如果+换成*,那么一个也不匹配
  5. Pattern pattern = Pattern.compile(regStr);
  6. Matcher matcher = pattern.matcher(content);
  7. while(matcher.find()){
  8. System.out.println("找到:" + matcher.group(0));
  9. }
  10. }

image.png

Pattern类

pattern对象是一个正则表达式对象。Pattern类没有公共构造方法。要创建一个 Pattern对象,调用其公共静态方法,它返回一个Pattern对象。该方法接受一个正则表达式作为它的第一个参数,比如: Pattern r = Pattern.compile(pattern);
Pattern类的matches方法常用于判断 正则表达式是否与整个字符串匹配成功。
image.png

Matcher类

Matcher 对象是对输入字符串进行解释和匹配的引擎,与Pattern类一样,Matcher 也没有公共构造方法。需要调用 Pattern 对象的 matcher 方法来获得一个Matcher对象。
image.png
image.png

  1. public static void main(String[] args) {
  2. String content = "hello edu jack tom hello smith hello hssss";
  3. String regStr = "hello";
  4. Pattern pattern = Pattern.compile(regStr);
  5. Matcher matcher = pattern.matcher(content);
  6. while(matcher.find()){
  7. System.out.println("找到:" + matcher.group(0));
  8. System.out.println(matcher.start());
  9. System.out.println(matcher.end());
  10. System.out.println(content.substring(matcher.start(),matcher.end()));
  11. }
  12. }

start和end实际上就是找到的对应子串 的开始索引和结束索引+1,因此可以使用substring方法截取。
image.png

  1. public static void main(String[] args) {
  2. String content = "hello edu jack tom hello smith hello hssss";
  3. String regStr = "hello";
  4. Pattern pattern = Pattern.compile(regStr);
  5. Matcher matcher = pattern.matcher(content);
  6. //把hello替换为你好,原content不变,返回新的字符串
  7. String newContent = matcher.replaceAll("你好");
  8. System.out.println(newContent);
  9. // while(matcher.find()){
  10. // System.out.println("找到:" + matcher.group(0));
  11. // System.out.println(matcher.start());
  12. // System.out.println(matcher.end());
  13. // System.out.println(content.substring(matcher.start(),matcher.end()));
  14. // }
  15. }

image.png

String类中使用正则表达式

replaceAll

  1. //使用正则表达式方式,将 JDK1.3 和 JDK1.4 替换成JDK
  2. //因为 . 在正则表达式中是有意义的,因此需要使用\\进行转义
  3. content = content.replaceAll("JDK1\\.3|JDK1\\.4","JDK");

验证字符串格式

验证一个手机号,要求必须是以138,139开头的。

  1. content = "13899999999";
  2. if(content.matches("1(38|39)\\d{8}")){
  3. System.out.println("验证成功");
  4. }else{
  5. System.out.println("验证失败");
  6. }

split

要求按照 # 或者 - 或者 ~ 或者 任意数字 来对字符串进行分割。

  1. public static void main(String[] args) {
  2. // \\d+ 表示任意数字
  3. String content = "hello#abc-jack12smith~郑州";
  4. String[] split = content.split("#|-|~|\\d+");
  5. for (String s : split) {
  6. System.out.println(s);
  7. }
  8. }

image.png