java零基础Ⅲ-- 7.正则表达式

连接视频



为什么要学习正则表达式

极速 体验正则表达式的威力

1、提取文章中所有英文单词

2、提取文章中所有的数字

3、提取文章中所有的英文单词和数字

4、提取百度热榜 标题

package com.zzpedu.repexp;

import java.util.regex.Matcher;
import java.util.regex.Pattern;

/**
 * 体验正则表达式,给我们文本处理带来哪些便利
 */
public class Regexp_ {
    public static void main(String[] args) {
        //假定,编写了爬虫,从百度页面得到如下文本
//        String content = "1995年,互联网的蓬勃发展给了Oak机会。业界为了使死板、单调的静态网页能够“灵活”起来," +
//                "急需一种软件技术来开发一种程序," +
//                "这种程序可以通过网络传播并且能够跨平台运行。于是,世界各大IT企业为此纷纷投入了大量的人力、物力和财力。这个时候," +
//                "Sun公司想起了那个被搁置起来很久的Oak,并且重新审视了那个用软件编写的试验平台," +
//                "由于它是按照嵌入式系统硬件平台体系结构进行编写的,所以非常小," +
//                "特别适用于网络上的传输系统,而Oak也是一种精简的语言,程序非常小,适合在网络上传输。" +
//                "Sun公司首先推出了可以嵌入网页并且可以随同网页在网络上传输的Applet(Applet是一种将小程序嵌入到网页中进行执行的技术)," +
//                "并将Oak更名为Java(在申请注册商标时,发现Oak已经被人使用了,再想了一系列名字之后," +
//                "最终,使用了提议者在喝一杯Java咖啡时无意提到的Java词语)。" +
//                "5月23日,Sun公司在Sun world会议上正式发布Java和HotJava浏览器。" +
//                "IBM、Apple、DEC、Adobe、HP、Oracle、Netscape和微软等各大公司都纷纷停止了自己的相关开发项目," +
//                "竞相购买了Java使用许可证,并为自己的产品开发了相应的Java平台。";

       // String content = "xxxxxxx <a target=\"_blank\" title=\"我是标题\" **********";

        String content = "私有地址\n" +
                "私有地址(Private address)属于非注册地址,专门为组织机构内部使用。\n" +
                "以下列出留用的内部私有地址\n" +
                "A类 10.0.0.0--10.255.255.255\n" +
                "B类 172.16.0.0--172.31.255.255\n" +
                "C类 192.168.0.0--192.168.255.255";


        //(1). 传统方法,使用遍历方式,代码量大,效率不高
        //(2). 正则表达式技术

        //1. 先创建一个Pattern对象,模式对象,可以理解成就是一个正则表达式对象
        //提取文章中所有英文单词
        //Pattern pattern = Pattern.compile("[a-zA-Z]+");
        //提取文章中所有的数字
        //Pattern pattern = Pattern.compile("[0-9]+");
        //提取文章中所有的英文单词和数字
        //Pattern pattern = Pattern.compile("([0-9]+)|([a-zA-Z]+)");
        //提取百度热榜 标题
        //Pattern pattern = Pattern.compile("<a target=\"_blank\" title=\"(\\S*)\"");
        //提取ip地址
        Pattern pattern = Pattern.compile("\\d+\\.\\d+\\.\\d+\\.\\d+");

        //2. 创建一个匹配器对象
        //理解:就是 matcher 匹配器按照 pattern(模式/样式), 到 content 文本中去匹配
        //找到就返回true,否则就返回false
        Matcher matcher = pattern.matcher(content);
        //3. 可以开始循环匹配
        int no = 0;
        while (matcher.find()){
            //匹配内容,文件,放到 m.group(0)
            System.out.println("找到: " + (++no) + " " + matcher.group(0));
            //System.out.println("找到: " + (++no) + " " + matcher.group(1));//提取百度热榜 标题 有小括号 使用 matcher.group(1)
        }
        
    }
}

执行效果:
在这里插入图片描述

**结论:**正则表达式是处理文本的利器


再提出几个问题?

1998年12月8日,第二代Java平台的企业版J2EE发布。1999年6月,Sun公司发布了第二代Java平台(简称为Java2)的3个版本:J2ME(Java2 Micro Edition,Java2平台的微型版),应用于移动、无线及有限资源的环境;J2SE(Java 2 Standard Edition,Java 2平台的标准版),应用于桌面环境;J2EE(Java 2Enterprise Edition,Java 2平台的企业版),应用于基于Java的应用服务器。Java 2平台的发布,是Java发展过程中最重要的一个里程碑,标志着Java的应用开始普及。

1、给你一个字符串(或者文章),请你找出所有四个数字在一起的字串?

2、给你一个字符串(或者文章),请你找出所有四个数字连在一起的字串,并且这四个数字要满足:第一位与第四位相同,第二位与第三位相同,比如:1221,5775

3、请验证输入的邮件,是否符合电子邮件格式

4、请验证输入的手机号,是否符合手机号格式


解决之道 - 正则表达式

1、为了解决上述问题,Java提供了正则表达式技术,专门用于处理类似文本问题

2、简单来说:正则表达式是对字符串执行模式匹配的技术

3、正则表达式:regular expression => 简写:RegExp



正则表达式基本介绍

介绍

1、一个正则表达式,就是用某种模式去匹配字符串的一个公式。很多因为它们看上去比较古怪而且复杂所以不敢去使用,不过,经过练习后,就觉得这些复杂的表达式写起来是相当简单的,而且,一旦你弄懂它们,你就能把数小时辛苦而且易错的文件处理工作缩短在几分钟(甚至几秒钟)内完成

2、这些要特别强调,正则表达式不是只有Java有,实际上很多编程语言都支持正则表达式进行字符串操作!

在这里插入图片描述



正则表达式底层实现(重要)

实例分析

为了让大家对正则表达式底层实现有一个直观的映射,举个例子

给你一段字符串(文本)请找出所有四个数字连在一起的子串,比如:

应该找到 1998 1999 3443 9889 ==》 分析底层实现

package com.zzpedu.repexp;

import java.util.regex.Matcher;
import java.util.regex.Pattern;

/**
 * 分析java的正则表达式的底层实现
 */
public class RegTheory {
    public static void main(String[] args) {
        String content = "1998年12月8日,第二代Java平台的企业版J2EE发布。" +
                "1999年6月,Sun公司发布了第二代Java平台(简称为Java2)的3个版本:J2ME(Java2 Micro Edition,Java2平台的微型版)," +
                "应用于移动、无线及有限资源的环境;J2SE(Java 2 Standard Edition,Java 2平台的标准版)," +
                "应用于桌面环境;J2EE(Java 2Enterprise Edition,Java 2平台的企业版)," +
                "应用3443于基于Java的应用服务器。Java 2平台的发布,是Java发展过程中最重要的一个里程碑," +
                "标志着Java的应用开始普及9889。";
        //目标:匹配所有四个数字
        //说明:
        //1. \\d 表示一个任意的数字
        String regStr = "\\d\\d\\d\\d";
        //2. 创建模式对象[即正则表达式对象]
        Pattern pattern = Pattern.compile(regStr);
        //3. 创建匹配器
        //说明:创建匹配器 matcher,按照 正则表达式的规则 去匹配 content字符串
        Matcher matcher = pattern.matcher(content);

        //4. 开始匹配
        while (matcher.find()){
            System.out.println("找到: " + matcher.group(0));
        }

    }
}

执行效果:

在这里插入图片描述

debug判断截图:

//1. \\d 表示一个任意的数字
String regStr = "\\d\\d\\d\\d";
//2. 创建模式对象[即正则表达式对象]
Pattern pattern = Pattern.compile(regStr);
//3. 创建匹配器
//说明:创建匹配器 matcher,按照 正则表达式的规则 去匹配 content字符串
Matcher matcher = pattern.matcher(content);

//4. 开始匹配
/**
 * matcher.find() 完成的任务
 * 1. 根据指定的规则,定位满足规则的字符串(比如1998)
 * 2. 找到后,将 子字符串的开始的索引记录到 matcher对象的属性中 int[] groups;
 *   groups[0] = 0,把该子字符串的结束的索引+1的值记录到 groups[1] = 4
 * 3. 同时记录oldLast 的值为 子字符串的结束的索引+1的值 即4,即下次执行find时,就从4开始匹配
 *
 *  matcher.group(0) 分析:
 *  源码:
 *  public String group(int group) {
 *         if (first < 0)
 *             throw new IllegalStateException("No match found");
 *         if (group < 0 || group > groupCount())
 *             throw new IndexOutOfBoundsException("No group " + group);
 *         if ((groups[group*2] == -1) || (groups[group*2+1] == -1))
 *             return null;
 *         return getSubSequence(groups[group * 2], groups[group * 2 + 1]).toString();
 *     }
 *   1. 根据 groups[0]=0 和 groups[1]=4 的记录的位置下标,从content 开始截取子字符串返回
 *      就是 [0,4) 包含 0 但是不包含索引为 4的位置
 *
 *   如果再次执行 find()方法,仍然上面分析来执行 1999
 *   groups[0] = 31,把该子字符串的结束的索引+1的值记录到 groups[1] = 35
 *   同时记录oldLast 的值为 子字符串的结束的索引+1的值 即35,即下次执行find时,就从35开始匹配
 */
while (matcher.find()){
    System.out.println("找到: " + matcher.group(0));
}

matcher.group属性初始值:
在这里插入图片描述

找个一个的值:

在这里插入图片描述

在这里插入图片描述

匹配第二个,截图:

在这里插入图片描述


带括号()正则表达式,匹配字符串,演示:

//1. \\d 表示一个任意的数字
String regStr = "(\\d\\d)(\\d\\d)";
//2. 创建模式对象[即正则表达式对象]
Pattern pattern = Pattern.compile(regStr);
//3. 创建匹配器
//说明:创建匹配器 matcher,按照 正则表达式的规则 去匹配 content字符串
Matcher matcher = pattern.matcher(content);

//4. 开始匹配
/**
 * matcher.find() 完成的任务 (考虑分组)
 * 什么是分组,比如:(\d\d)(\d\d) ,正则表达式中有() 表示分组,第1个() 表示第1组,第2个() 表示第2组...
 * 1. 根据指定的规则,定位满足规则的字符串(比如(19)(98))
 * 2. 找到后,将 子字符串的开始的索引记录到 matcher对象的属性中 int[] groups;
 *   2.1 groups[0] = 0,把该子字符串的结束的索引+1的值记录到 groups[1] = 4
 *   2.2 记录第1组()匹配到的字符串 groups[2] = 0 groups[3] = 2
 *   2.3 记录第2组()匹配到的字符串 groups[4] = 2 groups[5] = 4
 *   2.4 如果有更多分组....
 * 3. 同时记录oldLast 的值为 子字符串的结束的索引+1的值 即4,即下次执行find时,就从4开始匹配
 *
 *  matcher.group(0) 分析:
 *  源码:
 *  public String group(int group) {
 *         if (first < 0)
 *             throw new IllegalStateException("No match found");
 *         if (group < 0 || group > groupCount())
 *             throw new IndexOutOfBoundsException("No group " + group);
 *         if ((groups[group*2] == -1) || (groups[group*2+1] == -1))
 *             return null;
 *         return getSubSequence(groups[group * 2], groups[group * 2 + 1]).toString();
 *     }
 *   1. 根据 groups[0]=0 和 groups[1]=4 的记录的位置下标,从content 开始截取子字符串返回
 *      就是 [0,4) 包含 0 但是不包含索引为 4的位置
 *
 *   如果再次执行 find()方法,仍然上面分析来执行 1999
 */
while (matcher.find()){
    //小结:
    //1. 如果正则表达式有() 即分组
    //2. 取出匹配的字符串规则如下
    //3. group(0) 表示匹配到的子字符串
    //4. group(1) 表示匹配到的子字符串的第一组字串
    //5. group(2) 表示匹配到的子字符串的第二组字串
    //6. 以此类推.. 但是分组的数不能越界
    // 比如:正则表达式(\d\d)(\d\d) ==>  group(3)--> 越界
    System.out.println("找到: " + matcher.group(0));
    System.out.println("第1组()匹配到的值=" + matcher.group(1));
    System.out.println("第2组()匹配到的值=" + matcher.group(2));
}

执行效果:
在这里插入图片描述

debug断点截图:

第一次匹配:

在这里插入图片描述

在这里插入图片描述



正则表达式基本语法

基本介绍

如果想要灵活的运用正则表达式,必须了解其中各种元字符的功能,元字符从功能上大致分为:

  1. 限定符
  2. 选择匹配符
  3. 分组组合和反向引用符
  4. 特殊字符
  5. 字符匹配符
  6. 定位符

元字符(Metacharacter) - 转义符 \\

\\ 符号 说明:在我们使用正则表达式去检查某些特殊字符的时候,需要用到转义符号,否则检索不到结果,甚至会报错的。案例:用 $ 去匹配 "abc$(" 会怎么样?
( 去匹配 "abc$(" 会怎么样?

再次提示:
在Java 的正则表达式中,两个 \\ 代表其他语音的一个 \

/**
 * 演示转义字符串的使用
 */
public class Regexp02 {
    public static void main(String[] args) {
        String content = "abc$(abc(132(";
        //匹配(
        String regStr = "\\(";//  \\转义
        Pattern pattern = Pattern.compile(regStr);
        Matcher matcher = pattern.matcher(content);
        while (matcher.find()){
            System.out.println("找到: " + matcher.group(0));
        }
    }
}

执行效果:

在这里插入图片描述


元字符 - 转义符 \\

需要用到转义符号的字符有以下:
. * + () $ / \ ? [ ] ^ {}


元字符 - 字符匹配符

符号含义示例解释匹配输入
[ ]可接收的字符列表[efgh]e、f、g、h中任意1个字符
[^]不接收的字符列表[^abc]除a、b、c之外的任意1个字符,包括数字和特殊符号
-连字符A-Z任意单个大写字母
.匹配除 \n 以外的任何字符a..b以a开头,b结尾,中间包括2个任意字符的长度为4的字符串aaab、aefb、a35b、a#*b
\\d匹配单个数字字符,相当于[0-9]\\d{3}(\\d)?包含3个或4个数字的字符串123、9876
\\D匹配单个非数字字符,相当于[^0-9]\\D(\\d)*以单个非字符开头,后接任意个数子字符串a、A342
\\w匹配单个数字、大小写字母字符和下划线,相关于[0-9a-zA-Z_]\\d{3}\\w{4}以3个数字字符开头的长度为7的数字字母字符串234abcd、12345Pe
\\W匹配单个非数字、大小写字母字符,相当于[^0-9a-zA-Z]\\W+\\d{2}以至少1个非数字字母开头、2个数字字符结尾的字符串#29、#?@10

应用实例

1、[a-z] 说明:
[a-z] 表示可以匹配 a-z 之间的任意一个字符,比如:[a-z] 去匹配 a11c8 会得到什么结果?

2、java正则表达式默认是区分字母大小写的,如何实现不区分大小写

  • (?i)abc 表示 abc 都不区分大小写
  • a(?i)bc 表示 bc 不区分大小写
  • a((?i)b)c 表示只有 b 不区分大小写
  • Pattern pat = Pattern.complie(regEx, Pattern.CASE_INSENSITIVE);

[A-Z] 表示可以匹配 A-Z 之间的任意一个字符
[0-9] 表示可以匹配 0-9 之间的任意一个字符

3、[^a-z] 说明:
[^a-z] 表示可以匹配不是 a-z 中任意一个字符,比如:
[a-z] 去匹配 a11c8 会得到什么结果?用 [^a-z] {2} 去匹配 a11c8 又会得到什么结果?

[^A-Z] 表示可以匹配不是 A-Z 之间的任意一个字符
[^0-9] 表示可以匹配不是 0-9 之间的任意一个字符

/**
 * 演示字符匹配符 的使用
 */
public class Regexp03 {
    public static void main(String[] args) {
        String content = "a11c8aBc";
       //String regStr = "[a-z]";//匹配 a-z 之间任意一个字符
        //String regStr = "[A-Z]";//匹配 A-Z 之间任意一个字符
        //String regStr = "abc";//匹配 abc 字符串【默认区分大小写】
        //String regStr = "(?i)abc";//匹配 abc 字符串 【不区分大小写】
        //String regStr = "[0-9]";//匹配 0-9 之间任意一个字符
        //String regStr = "[^a-z]";//匹配 不在 a-z 之间任意一个字符
        String regStr = "[^0-9]";//匹配 不在 0-9 之间任意一个字符

        //说明:
        //1. 当创建Pattern对象时,指定 Pattern.CASE_INSENSITIVE,表示匹配时不区分字母大小写
        //Pattern pattern = Pattern.compile(regStr, Pattern.CASE_INSENSITIVE);
        Pattern pattern = Pattern.compile(regStr);
        Matcher matcher = pattern.matcher(content);
        while (matcher.find()){
            System.out.println("找到: " + matcher.group(0));
        }
    }

}

执行效果:
在这里插入图片描述


4、[abcd] 表示可以匹配 abcd 中的任意一个字符

5、[^abcd] 表示可以匹配不是 abcd 中的任意一个字符
当然上面 abcd 你可以根据实际情况修改,以适应你的需求

6、\\d 表示可以匹配 0-9 的任意一个数字,相当于 [0-9]

7、\\D 表示可以匹配不是 0-9 的任意一个数字,相关于 [^0-9]

8、\\w 表示可以匹配任意英文字符、数字和下划线,相关于 [0-9a-zA-Z_]

9、\\W 相关于 [^0-9a-zA-Z_]\\w 刚好相反

10、\\s 匹配任何空白字符(空格,制表符等)

11、\\S 匹配任何非空白字符,和 \\s 刚好相反

12、. 匹配出 \n 之外的所有字符,如果要匹配,本身则需要使用 \\.

/**
 * 演示字符匹配符 的使用
 */
public class Regexp03 {
    public static void main(String[] args) {
        String content = "a11c8a Bc_#";

        //String regStr = "[abcd]";//匹配在 abcd 中任意一个字符
        //String regStr = "\\D";//匹配 不在 0-9的任意一个字符
        //String regStr = "\\w";//匹配 大小写英文字母,数字下划线
        //String regStr = "\\W";//匹配 等价于 [^0-9a-zA-Z_]
        // \\s 匹配任何空白字符(空格,制表符等)
        //String regStr = "\\s";
        // \\S 匹配任何非空白字符,和 \\s 刚好相反
        //String regStr = "\\S";
        // . 匹配出 \n 之外的所有字符,如果要匹配,本身则需要使用 \\.
        String regStr = ".";
        
        Pattern pattern = Pattern.compile(regStr);
        Matcher matcher = pattern.matcher(content);
        while (matcher.find()){
            System.out.println("找到: " + matcher.group(0));
        }
    }
}

执行效果:

在这里插入图片描述


元字符 - 选择匹配符

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

符号含义示例解释
|匹配 | 之前或者之后的表达式ab|cdab或者cd
/**
 * 选择匹配符 | 使用
 */
public class Regexp04 {
    public static void main(String[] args) {
        String content = "zzp 哈哈 呵呵";

        String regStr = "zz|哈|嗯";

        Pattern pattern = Pattern.compile(regStr);
        Matcher matcher = pattern.matcher(content);
        while (matcher.find()){
            System.out.println("找到: " + matcher.group(0));
        }
    }
}

执行效果:

在这里插入图片描述


元字符 - 限定符

用于指定其前面的字符和组合项连续出现多少次,【java匹配默认贪婪匹配,即尽可能匹配多的】

符号符号示例说明匹配输入
*指定字符重复0次或n次(无要求)零到多次(abc)*仅包含任意个abc的字符串,等效于 \w*abc、abcabcabc
+指定字符重复1次或n次(至少一次)1到多m+(abc)*以至少1个m开头,后接任意个abc的字符串m、mabc、mabcabc、mmabc
?指定字符重复0次或者1次(最多一次)0到1m+abc?以至少1个m开头,后接ab或者abc的字符串macb、mabc、mmmab、mmabc
{n}只能输入n个字符[abcd]{3}由abcd中字母组成的任意长度为3的字符串abc、dbc、adc
{n,}指定至少 n 个匹配[abcd]{3,}由abcd中字母组成的任意长度不小于3的字符串aac、dbc、aaaadc
{n,m}指定至少 n 个但不多于 m 个匹配[abcd]{3,5}由abcd中字母组成的任意长度不小于3,不大于5的字符串aac、dbc、aaaaa、bdcad

举例说明:

1、{n} 说明:

n 表示出现的次数,比如:a{3}, 1{4}, (\\d){2}
但是这里要注意一点,1{3} 去匹配 1111111的话,会得到什么结果呢?

2、{n,m}说明:
n 表示至少出现的 n次最多m次,比如:a{3,4}, 1{4,5}, \\d{2,5}

3、+ 说明:
+ 表示出现1次到任意多次,比如: a+, 1+, \\d+

4、* 说明:
* 表示出现0次到任意多次,比如:a*, 1*, \\d*

5、? 说明:
? 表示出现0次到1次,比如:a?, 1?, \\d?

/**
 * 演示限定符的使用
 */
public class Regexp05 {
    public static void main(String[] args) {
        String content = "a2111111aaaahello";
        //a{3}, 1{4}, (\\d){2}
        //String regStr = "a{3}";//表示匹配 aaa
        //String regStr = "1{4}";//表示匹配 1111
        //String regStr = "\\d{2}";//表示匹配 两位的任意数字字符

        //a{3,4}, 1{4,5}, \\d{2,5}

        //细节:java匹配默认贪婪匹配,即尽可能匹配多的
        //String regStr = "a{3,4}";//表示匹配 aaa 或者 aaaa
        //String regStr = "1{4,5}";//表示匹配 1111 或者 11111
        //String regStr = "\\d{2,5}";//表示匹配 2位数或者3,4,5位数

        //1+
        //String regStr = "1+";//表示匹配 一个1或者多个1
        //String regStr = "\\d+";//表示匹配 一个数字或者多个数字

        //1*
        //String regStr = "1*";//表示匹配 0个1或者多个1
        //演示?的使用,默认也是 贪婪匹配
        String regStr = "a1?";//匹配 a 或者 a1

        Pattern pattern = Pattern.compile(regStr);
        Matcher matcher = pattern.matcher(content);
        while (matcher.find()){
            System.out.println("找到: " + matcher.group(0));
        }
    }
}

执行效果:
在这里插入图片描述


元字符 - 定位符

定位符,规定要匹配的字符串出现的位置,比如在字符串的开始还是在结束的位置,这个也是相关有用的,

符号符号示例说明匹配输入
^指定起始符^[0-9]+[a-z]*以至少1个数字开头,后接任意小写字母的字符串1234、6aa、55edfff
$指定结束符^[0-9]\\-[a-z]+$以至少1个数字开头,后接连字符 “-”,并以至少1个小写字母结尾的字符串1-a
\\b匹配目标字符串的边界han\\b这里说的字符串的边界是指子串间有空格,或者是目标字符串的结束位置hanshunping sphan nnhan
\\B匹配目标字符串的非边界han\\B\\b 的含义刚刚相反hanshunping sphan nnhan
/**
 * 演示定位符的使用
 */
public class Regexp06 {
    public static void main(String[] args) {
        //String content = "123-abc";
        String content = "hanshunping sphan nnhan";
        //以至少1个数字开头,后接任意小写字母的字符串
        //String regStr = "^[0-9]+[a-z]*";
        //以至少1个数字开头,后接连字符 “-”,并以至少1个小写字母结尾的字符串
        //String regStr = "^[0-9]+\\-[a-z]+$";

        //表示匹配边界的 han [这里的边界是指:被匹配的字符串最后,也可以是空格的子字符串的后面]
        //String regStr = "han\\b";
        //和 \\b 的含义刚好相反
        String regStr = "han\\B";

        Pattern pattern = Pattern.compile(regStr);
        Matcher matcher = pattern.matcher(content);
        while (matcher.find()){
            System.out.println("找到: " + matcher.group(0));
        }
    }
}

执行效果:

在这里插入图片描述


分组

常用分组

常用分组构造形式说明
(pattern)非命名捕获。捕获匹配的子字符串。编号为的第一个捕获是由整个正则表达式模式匹配文本,其它捕获结果根据左括号的顺序从1开始自动编号。
(?<name>pattern)命令捕获。将匹配的子字符串捕获到一个组名称或编号名称中。用于name的字符串不能包含任何标点符号,并且不能以数字开头。可以使用单引号替代尖括号,例如: (?'name')
/**
 * 分组
 */
public class Regexp07 {
    public static void main(String[] args) {
        String content = "hanshunping s7789 nn1189han";
        //下面就是非命名分组
        //1. matcher.group(0) 得到匹配的字符串
        //2. matcher.group(1) 得到匹配的字符串的第1个分组的内容
        //3. matcher.group(2) 得到匹配的字符串的第2个分组的内容
       // String regStr = "(\\d\\d)(\\d\\d)";//匹配4各个数字的字符串

        //命名分组:即可以给分组取名
        String regStr = "(?<g1>\\d\\d)(?<g2>\\d\\d)";//匹配4各个数字的字符串

        Pattern pattern = Pattern.compile(regStr);
        Matcher matcher = pattern.matcher(content);
        while (matcher.find()){
            System.out.println("找到: " + matcher.group(0));
            System.out.println("第1个分组的内容: " + matcher.group(1));
            System.out.println("第2个分组的内容: " + matcher.group(2));
            System.out.println("第1个分组的内容[通过组名]: " + matcher.group("g1"));
            System.out.println("第2个分组的内容[通过组名]: " + matcher.group("g2"));
        }
    }
}

执行效果:

在这里插入图片描述


特别分组

常用分组构造形式说明
(?:pattern)匹配 pattern 但不捕获该匹配的子表达式,即它是一个非捕获匹配,不存储供以后使用的匹配。这对于用 “or” 字符(|)组合模式部件的情况很有用。例如:'industr(?:y|ies)' 是比 'industry|industries' 更经济的表达式。
(?=pattern)它是一个非捕获匹配。例如:'Windows (?=95|98|NT|2000)' 匹配 "Windows 2000" 中的 "Windows",但不匹配 "Windows 3.1" 中的 "Windows"
(?!pattern)该表达式匹配不处于匹配 pattern 的字符串的起始点的搜索字符串。它是一个非捕获匹配。例如:'Windows (?!95|98|NT|2000)' 匹配 "Windows 3.1" 中的 "Windows",但不匹配 "Windows 2000" 中的 "Windows"

特别分组应用实例

给字符串 String content = “hello zzp教育 javazzp老师 zzp同学”;
使用非捕获分组完成,如下要求:
1、找到 zzp教育 zzp老师 zzp同学 子字符串
2、找到 zzp 这个关键字,但是要求只是查找 zzp教育 和 zzp同学 中包含有的 zzp
3、找到 zzp 这个关键字,但是要求只是查找 不是(zzp教育 和 zzp同学)中包含有的zzp

/**
 * 演示非捕获分组,语言比较特别
 */
public class Regexp08 {
    public static void main(String[] args) {
        String content = "hello zzp教育 javazzp老师 zzp同学";

        //1、找到  zzp教育  zzp老师 zzp同学 子字符串
        //String regStr = "zzp教育|zzp老师|zzp同学";
        //上面的写法可以等价非捕获分组,注意;不能 matcher.group(1)
        //String regStr = "zzp(?:教育|老师|同学)";

        //2、找到 zzp 这个关键字,但是要求只是查找 zzp教育 和 zzp同学 中包含有的 zzp
        //下面也是 非捕获分组,不能使用 matcher.group(1)
        //String regStr = "zzp(?=教育|同学)";

        //3、找到 zzp 这个关键字,但是要求只是查找 不是(zzp教育 和 zzp同学)中包含有的zzp
        //下面也是 非捕获分组,不能使用 matcher.group(1)
        String regStr = "zzp(?!教育|同学)";

        Pattern pattern = Pattern.compile(regStr);
        Matcher matcher = pattern.matcher(content);
        while (matcher.find()){
            System.out.println("找到: " + matcher.group(0));
        }
    }
}

执行效果:

在这里插入图片描述


非贪婪匹配

构造形式说明
?当此字符紧随如何其他限定符(*、+、?、{n}、{n,}、{n,m})之后时,匹配模式是 “非贪心的”。“非贪心的” 模式匹配搜索到的、尽可能短的字符串,而默认是"贪心的"模式匹配搜索到的、尽可能长的字符串。例如:在字符串 "oooo"中,"o+?"只匹配单个“o”,而"o+“匹配所有的"o”
/**
 * 非贪婪匹配
 */
public class Regexp09 {
    public static void main(String[] args) {
        String content = "hello111111 ok";
        //String regStr = "\\d+";//默认是贪婪匹配
        String regStr = "\\d+?";//非贪婪匹配

        Pattern pattern = Pattern.compile(regStr);
        Matcher matcher = pattern.matcher(content);
        while (matcher.find()){
            System.out.println("找到: " + matcher.group(0));
        }
    }
}

执行效果:

在这里插入图片描述



应用实例

1、汉字

2、邮政编码。要求:是1-9开头的一个六位数,比如:123890

3、QQ号码。要求:是1-9开头的一个(5位数-10位数),比如:12389、13456789

4、手机号码。要求:必须以13,14,15,18 开头的11位数,比如:13588889999

5、URL。比如:https://www.bilibili.com/video/BV1fh411y7R8?p=1

/**
 * 正则表达式的应用
 */
public class Regexp10 {
    public static void main(String[] args) {

        //1、汉字
        //String content = "你好a中国";
        //String regStr = "^[\u0391-\uffe5]+$";//汉字字符范围

        //2、邮政编码。要求:是1-9开头的一个六位数,比如:123890
        //String content = "123890";
        //String regStr = "^[1-9]\\d{5}$";

        //3、QQ号码。要求:是1-9开头的一个(5位数-10位数),比如:12389、13456789
        //String content = "13456789";
        //String regStr = "^[1-9]\\d{4,9}$";

        //4、手机号码。要求:必须以13,14,15,18 开头的11位数,比如:13588889999
        String content = "13588889999";
        String regStr = "^1[3|4|5|8]\\d{9}$";

        Pattern pattern = Pattern.compile(regStr);
        Matcher matcher = pattern.matcher(content);
        if (matcher.find()){
            System.out.println("满足格式");
        }else {
            System.out.println("不满足格式");
        }
    }
}

/**
 * 演示正则表达式的使用
 */
public class Regexp11 {
    public static void main(String[] args) {

        String content = "https://www.bilibili.com/video/BV1fh411y7R8?p#=894&spm_id_from=pageDriver";

        //5、URL。比如:https://www.bilibili.com/video/BV1fh411y7R8?p=1
        /**
         * 思路
         * 1. 先确定 url 开始的部分 https:// | http://
         * 2. 然后通过 ([\w-]+\.)+[\w-]+ 匹配 www.bilibili.com
         * 3. /video/BV1fh411y7R8?p=1 匹配 (\\/[\w-?=&/%.#]*)?
         */
        String regStr = "^((http|https)://)?([\\w-]+\\.)+[\\w-]+(\\/[\\w-?=&/%.#]*)?$";//注意:[. ? *]表示匹配就是.本身

        Pattern pattern = Pattern.compile(regStr);
        Matcher matcher = pattern.matcher(content);
        if (matcher.find()){
            System.out.println("满足格式");
        }else {
            System.out.println("不满足格式");
        }
    }
}

执行效果:

在这里插入图片描述



正则表达式三个常用类

java.util.regx 包主要包括以下三个类 PatternMatcher类 和 PatternSyntaxException

Pattern 类

pattern 对象是一个正则表达式对象。Pattern 类没有公共构造方法。要创建一个 Pattern 对象,调用其公共静态方法,它返回一个 Pattern对象。该方法接受一个正则表达式作为它的第一个参数,比如:Pattern r = Pattern.compile(pattern)


Matcher 类

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


PatternSyntaxException

PatternSyntaxException 是一个非强制异常类,它表示一个正则表达式模式中的语法错误。


Pattern 类的方法matches

用于整体匹配,比较简洁

import java.util.regex.Pattern;
public class Hello{
    public static void main(String[] args) {
		String content = "I am zzp from China";
		String pattern = ".*zzp.*";
		boolean isMatch = Pattern.matches(pattern,conten);
		System.out.println("是否整体匹配成功 " + isMatch);
	}
}

代码演示:

/**
 * 演示 matches 方法,用于整体匹配,在验证输入的字符串是否满足条件使用
 */
public class PatternMethod {
    public static void main(String[] args) {
        String content = "hello abc hello, zzp";
        //String regStr = "hello";
        String regStr = "hello.*";

        boolean matches = Pattern.matches(regStr, content);
        /*
           Pattern.matches底层源码
           public static boolean matches(String regex, CharSequence input) {
               Pattern p = Pattern.compile(regex);
               Matcher m = p.matcher(input);
               return m.matches();//Pattern.matches 底层是用了 Matcher.matches()方法
           }
         */
        System.out.println("整体匹配=" + matches);
    }
}

执行效果:

在这里插入图片描述


Matcher 类方法一览

说明
public int start() 返回以前匹配的初始索引。
public int start(int group) 返回以前匹配操作期间,有给定组成捕获的子序列的初始索引
public int end() 返回最后匹配字符之间的编移量。
public int end(int group) 返回以前匹配操作期间,有给定组成捕获的子序列的最后字符之后的编移量。
public boolean lookingAt() 尝试将从区域开头开始的输入序列与该模式匹配。
public boolean find() 尝试查找与该模式匹配的输入序列的下一个子序列。
public boolean find(int start) 重置此匹配器,然后尝试查找匹配该模式、从指定索引开始的输入序列的下一个子序列。
public boolean matches() 尝试将整个区域与模式匹配。
/**
 * Matcher 类的常用方法
 */
public class MatcherMethod {
    public static void main(String[] args) {
        String content = "hello edu jack zzpedutom hello smith hello zzpedu zzpedu";
        String regStr = "hello";

        Pattern compile = Pattern.compile(regStr);
        Matcher matcher = compile.matcher(content);
        while (matcher.find()){
            System.out.println("==========");
            System.out.println(matcher.start());//hello 的h索引 0,19,31
            System.out.println(matcher.end());//hello 的o索引   5,24,36
            System.out.println("找到:" + content.substring(matcher.start(),matcher.end()));//hello
        }
        //整体匹配方法,常用于,去效验某个字符串是否满足某个规则
        System.out.println("整体匹配=" + matcher.matches());

        //完成如果 content 有 zzpedu 替换成 哈哈了
        regStr = "zzpedu";
        compile = Pattern.compile(regStr);
        matcher = compile.matcher(content);
        //注意:返回的字符串才是替换后的字符串 原来的 content 不变化
        String newContent = matcher.replaceAll("哈哈了");
        System.out.println("newContent=" + newContent);
        System.out.println("content=" + content);
    }
}

执行效果:

在这里插入图片描述



分组、捕获、反向引用

提出需求

请看下面问题:

给你一段文本,请找出所有四位数字连在一起的子串,并且这四个数字要满足
1、第1位与第4位相同
2、第2位与第3位相同
比如:1221、5775 …


介绍

要解决前面的问题,我们需要了解正则表达式的几个概念:

1、分组
我们可以用圆括号组成一个比较复杂的匹配模式,那么一个圆括号的部分我们可以看作一个子表达式/一个分组

2、捕获
把正则表达式中子表达式/分组匹配的内容,保存到内存中以数字编号或显示命名的组里,方便后面引用,从左向右,以分组的左括号为标志,第一个出现的分组的组号为1,第二个为2,以此类推,组0代表的是整个正则式

3、反向引用
圆括号的内容被捕获后,可以在这个括号后被使用,从而写出一个比较实用的匹配模式,这个我们称为反向引用,这种引用既可以是在正则表达式内部,也可以是在正则表达式外部,内部反向引用 \\分组号,外部反向引用用 $分组号。


看几个小案例

1、要匹配两个连续的相同数字:(\\d)\\1
2、要匹配五个连续的相同数字:(\\d)\\1{4}
3、要匹配个位于千位相同,十位与百位相同的数 比如:5225、1551 (\\d)(\\d)\\2\\1

思考
请在字符串检索商品编号,形式如:12321-333999111 这样的号码,要求满足前面是一个五位数,然后一个-号,然后是一个九位数,连续的每三位要相同

/**
 * 反向引用
 */
public class Regexp12 {
    public static void main(String[] args) {
        String content = "1234e8976 hello33333 123211-333999111 jack14 1551 tom11 jack22 yyy xxx12345 ";
        //要匹配两个连续的相同数字:(\\d)\\1
        //String regStr = "(\\d)\\1";
        //要匹配五个连续的相同数字:(\\d)\\1{4}
        //String regStr = "(\\d)\\1{4}";
        //要匹配个位于千位相同,十位与百位相同的数 比如:5225、1551 (\\d)(\\d)\\2\\1
        //String regStr = "(\\d)(\\d)\\2\\1";

        /**
         * 请在字符串检索商品编号,形式如:12321-333999111 这样的号码,
         * 要求满足前面是一个五位数,然后一个-号,然后是一个九位数,连续的每三位要相同
         */
        String regStr = "\\d{5}-(\\d)\\1{2}(\\d)\\2{2}(\\d)\\3{2}";

        Pattern pattern = Pattern.compile(regStr);
        Matcher matcher = pattern.matcher(content);
        while (matcher.find()){
            System.out.println("找到: " + matcher.group(0));
        }
    }
}

执行效果:

在这里插入图片描述



正则表达式 - 应用实例

经典的结巴程序

把 类似 : “我…我要…学学学学…编程java!”;
通过正则表达式 修改成 “我要学习编程java”

public class Regexp13 {
    public static void main(String[] args) {
        String content = "我....我要....学学学学....编程java!";

        //1. 去掉.
        Pattern pattern = Pattern.compile("\\.");
        Matcher matcher = pattern.matcher(content);
        content = matcher.replaceAll("");
        System.out.println("content=" + content);

        //2. 去掉重复的字 我我要学学学学编程java!
        //思路
        //(1) 使用 (.)\\1+ 匹配前面与号码相同的字
        //(2) 使用 反向引用 $1 来替换匹配到的内容

        //注意:因为正则表达式变化了,所以需要重置 matcher
//        pattern = Pattern.compile("(.)\\1+");//分组的捕获内容记录到 $1
//        matcher = pattern.matcher(content);
//        while (matcher.find()){
//            System.out.println("找到: " + matcher.group(0));
//        }
//        //使用 反向引用 $1 来替换匹配到的内容
//        content = matcher.replaceAll("$1");
//        System.out.println("content=" + content);

        //3. 使用一条语句 去掉重复的字 我我要学学学学编程java!
        content = Pattern.compile("(.)\\1+").matcher(content).replaceAll("$1");
        System.out.println("content=" + content);
    }
}

执行效果:

在这里插入图片描述



String类中使用正则表达式

替换功能

Stringpublic String replaceAll(String regex, String replacement)

在这里插入图片描述

将上面文字的 JDK1.3 JDK1.4 统一替换成 JDK

public class StringReg {
    public static void main(String[] args) {
        String content = "2000年5月,JDK1.3、JDK1.4和J2SE1.3相继发布," +
                "几周后其获得了Apple公司Mac OS X的工业标准的支持。" +
                "2001年9月24日,J2EE1.3发布。2002年2月26日," +
                "J2SE1.4发布。自此Java的计算能力有了大幅提升";

        //使用正则表达式,将 JDK1.3、JDK1.4 替换成 JDK
        content = content.replaceAll("JDK1\\.3|JDK1\\.4", "JDK");
        System.out.println(content);
    }
}

执行效果:

在这里插入图片描述


判断功能

Stringpublic boolean matches(String regex){}

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

public class StringReg {
    public static void main(String[] args) {
        String content = "13812341234";

        //要求:验证一个手机号,要求必须是以 138 139开头的
        if(content.matches("1(38|39)\\d{8}")){
            System.out.println("验证成功");
        }else {
            System.out.println("验证失败");
        }
    }
}

执行效果:

在这里插入图片描述


分割功能

Stringpublic String[] split(String regex)

String content  = "hello#abc-jack12smith~北京";
要求按照 # 或者 - 或者 ~ 或者 数字 来分割
public class StringReg {
    public static void main(String[] args) {
        String content  = "hello#abc-jack12smith~北京";

        //要求按照 # 或者 - 或者 ~ 或者 数字 来分割
        String[] split = content.split("#|-|~|\\d+");
        for (String str : split){
            System.out.println(str);
        }
    }
}

执行效果:

在这里插入图片描述



作业

1、验证电子邮件格式是否合法

规定电子邮件规则如下:
a、只能有一个@
b、@前面是用户名,可以是a-z A-Z 0-9 _-字符
c、@后面是域名,并且域名只能是英文字母,比如:sohu.com 或者 tsinghua.org.cn
d、写出对应的正则表达式,验证输入的字符串是否为满足规则

public class Homework01 {
    public static void main(String[] args) {

        //a、只能有一个@
        //b、@前面是用户名,可以是a-z A-Z 0-9 _-字符
        //c、@后面是域名,并且域名只能是英文字母,比如:sohu.com 或者 tsinghua.org.cn
        //d、写出对应的正则表达式,验证输入的字符串是否为满足规则
        String content  = "zzp@sohu.com.cn";
        String regStr = "^[\\w-]+@([a-zA-Z]+\\.)+[a-zA-Z]+$";
        //说明
        //1. String 的 matches方法 是整体匹配 可以不加 ^..$(开始..结束)
        //2. 看看这个 matches方法 底层方法
        /* 源码
        String 类
            public boolean matches(String regex) {
                return Pattern.matches(regex, this);
            }
        Pattern 类
            public static boolean matches(String regex, CharSequence input) {
                Pattern p = Pattern.compile(regex);
                Matcher m = p.matcher(input);
                return m.matches();
            }
        Matcher 类 match方法
        Attempts to match the entire region against the pattern 翻译:尝试根据模式匹配整个区域。
            public boolean matches() {
                return match(from, ENDANCHOR);
            }

         */
        if(content.matches(regStr)){
            System.out.println("匹配成功");
        }else {
            System.out.println("匹配失败");
        }
    }
}

执行效果:
在这里插入图片描述


2、要求验证是不是整数或者小数

提示:这个题目考虑整数和负数
比如:123 -345 35.89 -89.1 -0.01 0.45 等

public class Homework02 {
    public static void main(String[] args) {
        //要求验证是不是整数或者小数
        //提示:这个题目考虑整数和负数
        //比如:123 -345 35.89 -89.1 -0.01 0.45 等
        /**
         * 思路:
         * 1. 先写出简单的正则表达式
         * 2. 在逐步完善[根据各种情况来完善]
         */
        String content = "-0.32";
        String regStr = "^[-+]?([1-9]\\d*|0)(\\.\\d+)?$";

        if(content.matches(regStr)){
            System.out.println("匹配成功 是整数或者小数");
        }else {
            System.out.println("匹配失败");
        }
    }
}

执行效果:

在这里插入图片描述


3、对一个url进行解析

http://www.sohu.com:8080/abc/index.html

a)要求得到协议是什么? – http
b)域名是什么? – www.sohu.com
c)端口是什么? – 8080
d)文件是什么? – index.html

思路:分组,4组,分别获取到对应的值

public class Homework03 {
    public static void main(String[] args) {

        String content = "http://www.sohu.com:8080/abc/sss/xxx/inde#$&x.html";
        //a)要求得到协议是什么? – http
        //b)域名是什么? – www.sohu.com
        //c)端口是什么? – 8080
        //d)文件是什么? – index.html
        //因为正则表达式是根据要求来编写的,所以,如果需求需要的话,可以改进
        String regStr = "^([a-zA-Z]+)://([a-zA-Z.]+):(\\d+)[\\w-/]*/([\\w.#$&]+)$";

        Pattern pattern = Pattern.compile(regStr);
        Matcher matcher = pattern.matcher(content);

        if(matcher.matches()){//整体匹配,如果匹配成功,可以通过group(x),获取对应分组的内容
            System.out.println("整体匹配成功=" + matcher.group(0));
            System.out.println("协议: " + matcher.group(1));
            System.out.println("域名: " + matcher.group(2));
            System.out.println("端口: " + matcher.group(3));
            System.out.println("文件: " + matcher.group(4));
        }else {
            System.out.println("没有匹配成功");
        }
    }
}

执行效果:

在这里插入图片描述

  • 0
    点赞
  • 0
    收藏
    觉得还不错? 一键收藏
  • 0
    评论
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值