Java学习笔记@正则表达式

一、正则表达式入门体验

1.1 实现提取文本中的英文单词

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

public class Main{
    public static void main(String[] args) {
        String content = "";
        // 创建Pattrn 对象,即正则表达式对象
        Pattern pattern = Pattern.compile("[a-zA-Z]+");
        // 创建匹配器对象, 使得 mather匹配器按照 pattern模式 到content文本中匹配
        Matcher matcher = pattern.matcher(content);
        // 循环匹配
        while(matcher.find()){
            // 匹配内容,文本放到m.group(0)
            System.out.println("找到: " + matcher.group(0));
        }
    }
}

1.2 提取数字

Pattern pattern = Pattern.compile("[0-9]+");

1.3 提取数字和英文字母

Pattern pattern = Pattern.compile("([0-9]+)|(a-zA-Z+)");

1.4 提取百度热搜标题

context是在百度随便一个页面的源代码里复制的,仅仅是为了测试正则表达式的强大功能。

Pattern pattern = Pattern.compile("<a target=\"_blank\" title=\"(\\S*)\"");

1.5 取 IP 地址

Pattern pattern = Pattern.compile("\\d+\\.\\d+\\.\\d+\\.+\\d+");

二、Matcher.find()

  1. 根据指定的规则定义满足规则的 子字符串,如2021

  2. 找到后,将子字符串开始的索引记录到matcher对象的属性 int [] groups; 把该子字符串的结束索引 + 1 的值记录到 groups[1]

  3. 同时记录 oldlast 的值为 子字符串结束的索引 + 1,即下次执行find的开始位置

  4. group方法源码:

   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();
    }
  • 根据groups[0]groups [1] 记录的位置,从 content开始截取子字符串返回
  1. 若再次执行 find 方法,仍按上述分析执行

三、正则表达式的分组

分组是什么? 如 (\d\d)(\d\d) ,正则表达式中有 () 则表示分组 ,第1个 () 表示 第 1 组, 第 2 个 () 表示 第 2组

  1. 根据指定规则,定位满足规则的子字符串,比如: (20) (21)
  2. 找到后,将子字符串开始索引记录到 mathcer对象的 int [] groups
    • groups[0] = 0 , 把该子字符串结束的索引+1的值记录到 groups[1]
    • 记录第1组() 匹配到的字符串 groups[2] = 0 groups[3] = 2
    • 记录第2组() 匹配到的字符串groups[4] = 2 groups[5] = 4
    • 如果有更多的分组,以此类推...

四、正则表达式语法

4.1 六大元字符

限定符、选择匹配符、分组组合和反向引用符、特殊字符、字符匹配符、定位符

4.2 特殊字符

什么时候会用到转义号 \\

当我们使用正则表达式去检索某些特殊字符的时候,不用转义号的话会检索不到结果,甚至会报错。

其他语言一般是用单个斜杠。

**特殊字符 **有 : . * + ( ) $ / \ ? [ ] ^ { }

4.3 字符匹配符

符号 含义 示例 解释
[ ] 可接受的字符列表 [efgh] e、f、g、h中的任意1个字符
[ ^ ] 不接受的字符列表 [^abc] 除 a、b、c之外的任意1个字符,包括数字和特殊符号
- 连字符 A-Z 任意单个大写字母
符号 含义 示例 说明 匹配输入
. 匹配除n以外的任何字符 a..b 以a开头,b结尾,中间包括2个任意字符的长度为4的字符串 aaabaefba35ba#*b
\\d 匹配单个数字字符,相当于[0-9] \\d{3}(\\d)? 包含3个或4个数字的字符串 1239876
\\D 匹配单个非数字字符,相当于[^0-9] \\D(\\d)* 以单个非数字字符开头,后接任意个数字字符串 aA342
\\w 匹配单个数字、大小写字母字符和下划线,相当于[0-9a-zA-Z] \\d{3}\\\w{4} 以3个数字字符开头的长度为7的数字字母字符串 234abcd12345Pe
\\W 匹配单个非数字、大小写字母字符,相当于[^0-9a-zA-Z] \\W+\\d{2} 以至少1个非数字字母字符开头,2个数字字符结尾的字符串 #29#?@10
\\s 匹配任何空白字符(空格,制表符TAB等)
\\S 匹配任何非空白字符,和\s 相反

4.3.1 应用案例

案例1 代码测试[a-z] 的正则匹配

[a-z] 说明[a-z] 表示可匹配a-z 中任意一个字符,比如 [a-z][A-Z] 去匹配 a11c8 会得到什么结果?

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

public class Main{
    public static void main(String[] args) {
        String content = "a11c8";
        String regStr1 ="[a-z]";
        String regStr2 = "[A-Z]";
        Pattern pattern = Pattern.compile(regStr1);
        System.out.println(regStr1 + " 的结果: ");
        print(regStr1, content);
        System.out.println(regStr2 + " 的结果: ");
        print(regStr2, content);
    }
    public static void print(String reg, String c){
        Pattern p= Pattern.compile(reg);
        Matcher matcher = p.matcher(c);
        while(matcher.find()){
            System.out.println("找到: " + matcher.group(0));
        }
    }
}

运行结果:

[a-z] 的结果:
找到: a
找到: c
[A-Z] 的结果:

案例2 字母大小写问题

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

(?i) abc 表示 abc 都不区分大小写

a(?i) bc 表示 bc 不区分大小写

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

另外一种方法 , 在创建pattern 对象时多加一个参数

Patten pat = Pattern.compile(reg, Pattern.CASE_INSENSITIVE);

此时指定匹配时都不区分字母大小写

案例3 ^ 符号的运用

[^ a-z] 表示可以匹配不是a-z中的任意一个字符,比如用它去匹配a11c8 看看结果?用[^a-z]{2} 呢?

由于和案例2类似就不敲代码了,需要注意的是后面的{2} 是表示需要 **连续两个 **同时满足的条件

4.4 选择匹配符

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

符号 符号 示例 解释
` ` 匹配` ` 之前之后的表达式

4.5 限定符

符号 含义 示例 说明 匹配输入
* 指定字符重复0次或 n 次(无要求) (abc)* 仅包含任意个abc的字符串,等效于\w* abcabcabcabc
+ 指定字符重复1次或n次(至少1次) m+(abc)* 以至少1个m开头,后接任意个abc的字符串 mmabcmabcabc
? 指定字符重复0次或1次(最多1次) m+abc? 以至少1个m开头,后接ab或abc的字符串 mabmabcmmmabmmabc
{n} 只能输入n个字符 [abcd]{3} 由abcd中字母组成的任意长度为3的字符串 abcdbcadc
{n, } 指定至少 n 个匹配 [abcd]{3, } 由 abcd中字母组成的任意长度不小于3的字符串 aabdbcaaabdc
{n, m} 必定至少n个但不多于m个匹配 [abcd]{3,5} 由abcd中字母组成的任意长度不小于3且不大于5的字符串 abcabcdaaaaabcdab

4.6 贪婪匹配

Java匹配默认是贪婪匹配,即:尽可能匹配多的元素

非贪婪匹配符号?

当字符? 紧随任何其他限定符(*、+、?、{n}、{n, }、{n,m}) 之后时,匹配模式是“非贪心的"。

即匹配尽可能短的字符串 ,例如:

在字符串oooo 中 ,o+? 只匹配单个o ,而o+ 匹配所有o

4.7 定位符

符号 含义 示例 说明 匹配输入
^ 指定起始字符 ^[0-9]+[a-z]* 以至少1个数字开头,后接任意个小写字母的字符串 1236aa555edf
` 符号 含义 示例 说明
----- ---------------------- ------------------ ------------------------------------------------------------ ----------------------
^ 指定起始字符 ^[0-9]+[a-z]* 以至少1个数字开头,后接任意个小写字母的字符串 1236aa555edf
| 指定结束字符 | ^[0-9]\\-[a-z]+$ 以1个数字开头后接连字符- ,并以至少1个小写字母结束的字符串 1-a
\\b 匹配目标字符串的边界 cc\\b 这里说的字符串的边界指的是字串间有空格,或者是目标字符串的结束位置 hhccppcc
\\B 匹配目标字符串的非边界 cc\B \b的含义刚刚相反 ccy

4.8 分组

常用分组构造形式 说明
(pattern) 非命名捕获。捕获匹配的子字符串。编号为零的第一个捕获是由整个正则表达式模式匹配的文本,其他捕获结果则根据左括号的顺序从开始自动编号。
(? < name> pattern) 命名捕获,将匹配的子字符串捕获到一个组名称或编号名称中。用于name 的字符串不能包含任何标点符号,并且不能以数字开头。可以使用单引号替代尖括号。例如 ?('name')

4.8.1 非命名捕获

import sun.security.rsa.RSAUtil;

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

public class Main{
    public static void main(String[] args) {
        String content = "test2021pkpk1314";
        /* 下面为非命名分组
         说明
         1. matcher.group(0) 得到匹配得到的字符串
         2. matcher.group(1) 得到匹配的字符串的第1个分组内容
         ... 以此类推
        */
        String regStr = "(\\d\\d)(\\d\\d)";
        Pattern pattern = Pattern.compile(regStr);
        Matcher matcher = pattern.matcher(content);
        while(matcher.find()){
            System.out.println("找到 = " + matcher.group(0));
            System.out.println("第一组内容: " + matcher.group(1));
            System.out.println("第二组内容: " + matcher.group(2));
        }
    }
}

输出结果:

找到 = 2021
第一组内容: 20
第二组内容: 21
找到 = 1314
第一组内容: 13
第二组内容: 14

4.8.2 命名分组

import sun.security.rsa.RSAUtil;

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

public class Main{
    public static void main(String[] args) {
        String content = "test2021pkpk1314";
        /* 下面为非命名分组
         说明
         1. matcher.group(0) 得到匹配得到的字符串
         2. matcher.group(1) 得到匹配的字符串的第1个分组内容
         ... 以此类推
        */
        String regStr = "(?<g1>\\d\\d)(?<g2>\\d\\d)";
        Pattern pattern = Pattern.compile(regStr);
        Matcher matcher = pattern.matcher(content);
        while(matcher.find()){
            System.out.println("找到 = " + matcher.group(0));
            System.out.println("第一组内容: " + matcher.group("g1"));
            System.out.println("第二组内容: " + matcher.group("g2"));
        }
    }
}

输出结果和上述相同

4.8.3. 特别的非捕获分组

常用分组构造形式 说明
(?:pattern) 匹配pattern 但不捕获该匹配的子表达式,即它是一个非捕获匹配,不存储供以后使用的匹配。这对于用or字符(I) 组合模式不见的情况很有用,例如 `industr(?:y
(?=pattern) 非捕获匹配。例如`Windows(?=95
(?!pattern) 该表达式匹配不处于匹配pattern 的字符串的起始点的搜索字符串也是一个非捕获匹配。例如`Windows(?!95
import sun.security.rsa.RSAUtil;

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

public class Main{
    public static void main(String[] args) {
        String content = "qwtoi测试1qwti测试2哦哦测试3qwtp";
        String regStr = "测试(?:1|2|3)";
        Pattern pattern = Pattern.compile(regStr);
        Matcher matcher = pattern.matcher(content);
        while(matcher.find())
            System.out.println("找到 = " + matcher.group(0));
    }
}

输出结果:

找到 = 测试1
找到 = 测试2
找到 = 测试3

五、正则表达式应用示例

5.1 检查是否全为汉字

正则表达式:^[\u0391-\uffe5]+$

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

public class Main{
    public static void main(String[] args) {
        print("测试一下");
        print("测试1下");
        print("ok");
    }
    public static void print(String content){
        String regStr = "^[\u0391-\uffe5]+$";
        Pattern pattern = Pattern.compile(regStr);
        Matcher matcher = pattern.matcher(content);
        System.out.print(content +" ->检查结果: ");
        System.out.println(matcher.find() ? "YES": "NO");
    }
}

输出结果:

测试一下 ->检查结果: YES
测试1下 ->检查结果: NO
ok ->检查结果: NO

5.2 检查是否为5-10位数字

思路:限制开头是1-9的数字,之后用{} 限制个数和结尾

正则表达式:^[1-9]\\d{4, 10}$

5.3 检查手机号码

要求:必须以13,14,15,18开头的11位,比如13366667777

正则表达式:^1[3|4|5|8]\\d{9}$

5.4 分析B站视频网站的URL

测试文本

https://www.bilibili.com/video/BV1ov4115757

提取头 和 BV号

正则表达式:^(?<g1>(https|http)://)([\\w.]+\\/)+(?<BV>BV+[\\w]+)+

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

public class Main{
    public static void main(String[] args) {
        String content = "https://www.bilibili.com/video/BV1ov4115757";
        String regStr = "^(?<g1>(https|http)://)([\\w.]+\\/)+(?<BV>BV+[\\w]+)+";
        Pattern pattern = Pattern.compile(regStr);
        Matcher matcher = pattern.matcher(content);
        while(matcher.find()) {
            System.out.println(matcher.group("g1"));
            System.out.println(matcher.group("BV"));
        }
    }
}

六、正则表达式三大常用类

6.1. Pattern类

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

matches 方法

返回值类型:boolean

作用:返回一个字符串的整体是否与正则表达式匹配,不用规定开头,默认从0开始匹配。

例如:

import java.util.regex.Pattern;

public class Main{
    public static void main(String[] args) {
        String content = "hello qwt 额";
        String regStr = ".*qw.*";
        System.out.println("整体匹配结果: " + Pattern.matches(regStr, content));
    }
}

输出结果:

整体匹配结果:true

matches方法 源码:

    public static boolean matches(String regex, CharSequence input) {
        Pattern p = Pattern.compile(regex);
        Matcher m = p.matcher(input);
        return m.matches();
    }

 	public boolean matches() {
        return match(from, ENDANCHOR);
    }

6.2 Matcher类

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

常见方法

方法一览 其中 public省略

方法名 说明
int start() 返回以前匹配的初始索引
int start(int group) 返回在以前的匹配操作期间,由给定组所捕获的子序列的初始索引
int end() 返回最后匹配字符之后的偏移量
end(int group) 返回在以前的匹配操作期间,由给定组所捕获的子序列的最后字符偏移量+1
boolean lookingAt() 尝试将从区域开头开始的输入序列与该模式匹配
find() 尝试查找与该模式匹配的输入序列的下一个子序列
find(int start) 重置此匹配器,然后尝试查找匹配该模式、从指定索引开始的输入序列的下一个子序列
boolean mathes() 尝试将整个区域与模式匹配
Matcher appendReplacement(StringBuffer x, String replacement) 实现非终端添加和替换步骤
StringBuffer appendTail(StringBuffer x) 实现终端添加和替换步骤
String replaceAll(String replacement) 替换模式与给定替换字符串相匹配的输入序列的每个子序列,不改变原字符串
String replaceFirst(String replacement) 替换模式与给定替换字符串匹配的输入序列的第一个子序列
static String quoteReplacement(String s) 返回指定字符串的字面替换字符串。这个方法返回一个字符串,就像传递给Matcher 类的appendReplacement 方法一个字面字符串一样工作。

6.3 PatternSyntaxException类

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

七. 分组、捕获、反向引用三者的关系

7. 1 概念

分组

可用 圆括号 ()组成一个比较复杂的匹配模式,那么一个圆括号()的部分就可看做是一个 子表达式 / 分组

捕获

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

反向引用

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

7.2 反向引用案例

内部案例

  1. 匹配两个连续的相同数字 : (\\d)\\1

  2. 匹配五个连续的相同数字:(\\d)\\1{4}

  3. 匹配个位与千位相同,十位与百位相同的数:(5225,1551) (\\d)(\\d)\\2\\1

  4. 检索一个商品编号,形式如:12321-333999111这样的号码,即:

    要求满足前面为五位数、后面为九位数、中间用 - 号连接,并且九位数为AAABBBCCC的形式

    (\\d{5}-(\\d)\\1{2}(\\d)\\2{2}(\\d)\\3{2})

外部引用去重

经典结巴程序

把 类似: 我...我要....学学学学....编程java!

通过正则表达式 改成我要学编程java

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

public class Main{
    public static void main(String[] args) {
        String content = "我...我要....学学学学....编程java";
        String regStr = "\\.";
        Pattern pattern = Pattern.compile(regStr);
        Matcher matcher = pattern.matcher(content);
        // 1. 去掉所有的点
        content = matcher.replaceAll("");
        // 2. 去掉重复的字
        /*
          思路: (1) 使用(.)\\1+  查找2个及以上连续且相同的字符
                (2) 使用反向引用 $1 来替换匹配到的内容
         */
        pattern = Pattern.compile("(.)\\1+");   // 分组的捕获内容记录到 $1
        matcher = pattern.matcher(content);

        // 反向引用 替换
        content = matcher.replaceAll("$1");
        System.out.println(content);
    }
}

输出结果:

我要学习编程java

简化版本

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

public class Main{
    public static void main(String[] args) {
        String content = "我...我要....学学学学....编程java";
        String regStr = "\\.";
        Pattern pattern = Pattern.compile(regStr);
        Matcher matcher = pattern.matcher(content);
        System.out.println(
                Pattern.compile("(.)\\1+").
                matcher(matcher.replaceAll("")).
                replaceAll("$1"));
    }
}

八、梦幻联动 | String类中使用正则表达式

8.1 replaceAll () 方法

将文本

2000年5月,JDK1.3、JDK1.4和J2SE1.3相继发布," +
"几周后其获得了Apple公司Mac OS X的工业标准的支持。

中的JDK1.3 和 JDK1.4 替换成 JDK

public class Main{
    public static void main(String[] args) {
        String content = "2000年5月,JDK1.3、JDK1.4和J2SE1.3相继发布," +
                "几周后其获得了Apple公司Mac OS X的工业标准的支持。";
        System.out.println(content.replaceAll("JDK1\\.3|JDK1\\.4", "JDK"));
    }
}

8.2 matches () 方法

验证一个手机号,要求必须以133、132开头

public class Main{
    public static void main(String[] args) {
        String[] p = {"13212341234", "133", "1331224123", "132123412345","0"};
        for(String s : p)
            System.out.println(
                    s.matches("1(33|32)\\d{8}") ?
                            s + "->True":
                            s + "->False");
    }
}

输出结果:

13212341234->True
133->False
1331224123->False
132123412345->False
0->False

8.3 split()方法

按照 # 或者 - 或者 ~ 或者 数字分割字符串

public class Main{
    public static void main(String[] args) {
        String content = "hello#abc-java8test~加油";
        for(String x : content.split("#|-|~|\\d+"))
            System.out.println(x);
    }
}

输出结果:

hello
abc
java
test
加油

九、练习巩固 | 又觉得自己行了吗?先来试试这些案例

9.1 验证电子邮件

规定电子邮件的规则为:

  1. 只能有一个@
  2. @前面为用户名, 可以是a-z 、 A-Z、0-9、-_字符
  3. @后面为域名,并且域名只能是英文字母,如:sohu.com
  4. 写出对应的正则表达式,验证输入的字符串是否为满足规则

正则表达式:[\\w-]+@([a-zA-Z]+\\.)+[a-zA-Z]+

import java.util.Scanner;

public class Main{
    public static void main(String[] args) {
        Scanner in = new Scanner(System.in);
        print();
        String x = in.next();
        while(!"q".equals(x)){
            System.out.println(isOK(x) ? "YES" : "NO");
            print();
            x = in.next();
        }
    }
    public static void print(){
        System.out.println("电子邮件检验系统 -> 输入邮箱直接检验,输入q退出系统");
    }
    public static boolean isOK(String content){
        String regStr = "[\\w-]+@([a-zA-Z]+\\.)+[a-zA-Z]+";
        return content.matches(regStr); // 整体匹配
    }
}

9.2 验证整数、小数、整数负数

RT,特殊情况, +- 可表示数字的符号,数字不能由多余的前导0,如01.1 非法

正则表达式:^[-+]?([1-9]\\d*|0)(\\.\\d+)?$

import java.util.Scanner;

public class Main{
    public static void main(String[] args) {
        Scanner in = new Scanner(System.in);
        print();
        String x = in.next();
        while(!"q".equals(x)){
            System.out.println(isOK(x) ? "YES" : "NO");
            print();
            x = in.next();
        }
    }
    public static void print(){
        System.out.println("数字检验系统 -> 输入数字直接检验,输入q退出系统");
    }
    public static boolean isOK(String content){
        String regStr = "^[-+]?([1-9]\\d*|0)(\\.\\d+)?$";
        return content.matches(regStr); // 整体匹配
    }
}

9.3 全面解析URL

协议、域名、端口、文件名

前提:网址无特殊符号

输入样例1

http://www.baidu.com:8080/abc/test/index.html

输出样例1

整体匹配= http://www.baidu.com:8080/abc/test/index.html
协议: http
域名: www.baidu.com
端口: 8080
文件: index.html

输入样例2

http://www.baidu.com:8080/abc/test///index.html

输出样例2

整体匹配= http://www.baidu.com:8080/abc/test///index.html
协议: http
域名: www.baidu.com
端口: 8080
文件: index.html

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

public class Main{
    public static void main(String[] args) {
        Scanner in = new Scanner(System.in);
        print();
        String x = in.next();
        while(!"q".equals(x)){
            run(x);
            print();
            x = in.next();
        }
    }
    public static void print(){
        System.out.println("URL分析系统 -> 输入网址直接分析,输入q退出系统");
    }
    public static void run(String content){
        String regStr = "^([a-zA-Z]+)://([a-zA-Z.]+):(\\d+)[\\w-/]*/([\\w.]+)$";
        Pattern pattern = Pattern.compile(regStr);
        Matcher matcher = pattern.matcher(content);
        if(matcher.matches()){ // 整体匹配
            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("分析结果为空.");
    }
}
posted @ 2021-11-02 00:26  Unirithe  阅读(279)  评论(0)    收藏  举报