Java正则表达式学习笔记

学习来源:韩顺平讲Java

学习时间:2022年3月11日

1 概述

正则表达式:regular expression,简写为RegExpregex,专门用于解决处理文本问题,是对字符串执行模式匹配的技术。

很多编程语言都支持正则表达式进行字符串操作。

regex体验

给出一段文本:

1
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平台。

需求:

  • 提取文章中所有的英文单词
  • 提取文章中所有的数字
  • 提取文章中所有的英文单词和数字
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
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平台。";
// 方法一:传统方法:遍历,代码量大,效率低
// 方法二:正则表达式

// 1.创建Pattern对象,模式对象,可以理解为一个正则表达式对象
// 匹配英文单词
// Pattern pattern = Pattern.compile("[a-zA-Z]+");

// 匹配数字
// Pattern pattern = Pattern.compile("[0-9]+");

// 匹配英文单词和数字
Pattern pattern = Pattern.compile("([0-9]+)|([a-zA-Z]+)");

// 2.创建一个匹配器对象
// 理解:matcher匹配器按照pattern(模式/样式),到content文本中匹配,找到就返回true
Matcher matcher = pattern.matcher(content);
// 3.循环匹配
while (matcher.find()) {
// 匹配的内容放到m.group()中
System.out.println("找到: " + matcher.group(0));
}
}
}

打印结果:(列举部分)

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
找到: 1995
找到: Oak
找到: IT
找到: Sun
找到: Oak
找到: Oak
找到: Sun
找到: Applet
找到: Applet
找到: Oak
找到: Java
找到: Oak
找到: Java
找到: Java
找到: 5
找到: 23
找到: Sun
找到: Sun

2 底层实现

2.1 代码示例1

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
public class RegTheory {
public static void main(String[] args) {
String content = "1998年12月8日,第二代Java平台的企业版J2EE发" +
"布。1999年6月,Sun公司发布了第二代Java平台(简称为Java" +
"2)的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 matcher = pattern.matcher(content);
// 4.开始匹配
while (matcher.find()) {
System.out.println("find: " + matcher.group(0));
}
}
}
  • matcher.find()

    • 根据指定的规则,定位满足规则的子字符串(例如1998
    • 找到后,将子字符串的开始索引和结束索引+1记录在matcher对象的属性 int[] groups;
    1
    2
    3
    4
    // 开始位置的索引0,记录在group[0]
    groups[0] = 0;
    // 结束位置的索引+1,记录在group[1]
    groups[1] = 4;
    • 同时,记录oldLast的值为子字符串的结束索引+1(即4),再次执行find方法时,从oldLast记录的索引值开始进行匹配,同时groups数组全部重置为-1
  • matcher.group(0)

    • 根据groups[0] = 0groups[1] = 4的记录的位置,从content开始截取子字符串并返回,即[0, 4)之间的子串
1
2
3
4
5
6
7
8
9
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();
}
  • 输出1998后,再次进行匹配,定位至1999,此时:
1
2
3
4
// 开始位置的索引0,记录在group[0]
groups[0] = 31;
// 结束位置的索引+1,记录在group[1]
groups[1] = 35;

2.2 代码示例2

考虑分组的情况,即regex中含有小括号的情况。第一对小括号表示第一组,以此类推。

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
public class RegTheory {
public static void main(String[] args) {
String content = "1998年12月8日,第二代Java平台的企业版J2EE发" +
"布。1999年6月,Sun公司发布了第二代Java平台(简称为Java" +
"2)的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 matcher = pattern.matcher(content);
// 4.开始匹配
while (matcher.find()) {
System.out.println("find: " + matcher.group(0));
System.out.println("第1组()匹配到的值: " + matcher.group(1));
System.out.println("第2组()匹配到的值: " + matcher.group(2));
}
}
}

打印结果:

1
2
3
4
5
6
7
8
9
10
11
12
find: 1998
第1组()匹配到的值: 19
第2组()匹配到的值: 98
find: 1999
第1组()匹配到的值: 19
第2组()匹配到的值: 99
find: 3443
第1组()匹配到的值: 34
第2组()匹配到的值: 43
find: 9889
第1组()匹配到的值: 98
第2组()匹配到的值: 89
  • matcher.find()

    • 根据指定的规则,定位满足规则的子字符串(例如(19)(98)

    • 找到后,将子字符串和分组的开始索引和结束索引+1记录在matcher对象的属性 int[] groups;,此时groups数组的取值为:

    1
    2
    3
    4
    5
    6
    7
    8
    9
    10
    11
    12
    13
    // 1998 索引从左到右分别为0, 1, 2, 3
    groups[0] = 0;
    groups[1] = 4;

    // 第一组:19 索引从左到右分别为0, 1
    groups[2] = 0;
    groups[3] = 2;

    // 第二组:98 索引从左到右分别为2, 3
    groups[4] = 2;
    groups[5] = 4;

    // 如果有更多的分组,以此类推
  • 小结:如果正则表达式有分组,取出匹配的字符串规则如下:

    • groups(0):表示匹配到的子串
    • groups(1):表示匹配到的子串的第一组子串
    • groups(2):表示匹配到的子串的第二组子串
    • 分组不能够越界
    • 以此类推

3 正则表达式语法

正则表达式中的元字符从功能上大致分为:

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

3.1 正则转义符

转义符\\:使用正则表达式去撇皮某些特殊字符时,需要用到转义符。注意在java中,转义符号用两个反斜杠\

需要用到转义符号的字符有:. * + ( ) $ / \ ? [ ] ^ { },注意,在[]中不需要转义,会自动转义,例如[?]匹配的就是一个实实在在的?

代码示例

1
2
3
4
5
6
7
8
9
10
11
12
13
// 演示转义字符的使用
public class Regex01 {
public static void main(String[] args) {
String content = "abc$(abc(123(";
// 匹配(
String regStr = "\\(";
Pattern pattern = Pattern.compile(regStr);
Matcher matcher = pattern.matcher(content);
while (matcher.find()) {
System.out.println("find: " + matcher.group(0));
}
}
}

输出:

1
2
3
find: (
find: (
find: (

3.2 字符匹配符

3.2.1 概述

符号 含义 示例 说明
[] 可接受的字符列表 [efgh] efgh中的任意一个字符,可自动转义
[^] 不可接受的字符列表 [^abc] 除abc之外的任意一个字符,包括数字和特殊符号
- 连字符 A-Z 任意单个大写字母
. 匹配除\n以外的任何一个字符 a..b 以a开头,b结尾,中间包括2个任意字符的长度为4的字符串
\\d 匹配单个数字字符,相当于[0-9] \\d{3}(\\d)? 包含3个或4个数字的字符串
\\D 匹配单个非数字字符,相当于[^0-9] \\D(\\d)* 以单个非数字字符开头,后接任意个数字的字符串
\\w 匹配单个数字、大小写字母字符,下划线,相当于[0-9a-zA-Z_] \\d{3}\\w{4} 以3个数字字符开头的长度为7的数字字母字符串
\\W 匹配单个非数字、非大小写字母字符,非下划线,相当于[^0-9a-zA-Z_] \\W+\\d{2} 以至少1个非数字字母字符开头,2个数字字符结尾的字符串
\\s 匹配单个任意空白字符(例如空格,制表符等)
\\S 匹配单个任意非空白字符
  • 注意

    • \\d{3}:表示连续三个数字字符,等价于\\d\\d\\d
    • (\\d)?:表示可能有0个或1个数字字符
    • 其他符号后面再讲
  • java正则表达式默认是区分大小写的,但可以实现不区分大小写

    • 方法一:利用(?i)

      • (?i)abc:表示abc都不区分大小写
      • a(?i)bc:表示bc不区分大小写
      • a((?i)b)c:表示b不区分大小写
    • 方法二:设置大小写不敏感

      1
      Pattern pattern = Pattern.compile(regStr, Pattern.CASE_INSENSITIVE);

3.2.2 代码演示

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
// 字符匹配符的使用
public class Regex02 {
public static void main(String[] args) {
String content = "a11c8";

// 1.匹配a到z中任意一个字符
String regStr1 = "[a-z]"; // a,c

// 2.匹配A到Z中任意一个字符
String regStr2 = "[A-Z]"; // null

// 3.匹配abc字符串
String regStr3 = "abc"; // null

// 4.匹配abc字符串,不区分大小写
String regStr4 = "(?i)abc"; // null

// 5.匹配不在a-z之间的任意连续两个字符
String regStr5 = "[^a-z]{2}"; // 11

// 6.匹配不在0-9之间的任意单个字符
String regStr6 = "[^0-9]"; // a,c

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

while (matcher.find()) {
System.out.println("find: " + matcher.group(0));
}
}
}

3.3 选择匹配符

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

符号 含义 示例 说明
` ` 匹配` `之前或之后的表达式

代码示例

1
2
3
4
5
6
7
8
9
10
11
12
13
14
// 选择匹配符使用
public class Regex03 {
public static void main(String[] args) {
String content = "Hongyi Mark 四川省成都市";
String regStr = "(?i)hong|四川|成都";

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

while (matcher.find()) {
System.out.println("find: " + matcher.group(0));
}
}
}

打印结果:

1
2
3
find: Hong
find: 四川
find: 成都

3.4 限定符

限定符用于指定前面的字符和组合项连续出现多少次。

符号 含义 示例 说明
* 指定字符重复0次或n次,零到多 (abc)* 仅包含任意个abc的字符串
+ 指定字符重复1次或n次,一到多 m+(abc)* 以至少1个m开头,后接任意个abc的字符串
? 指定字符重复0次或1次,零到一 m+abc? 以至少1个m开头,后接abc或ab的字符串
{n} 只能输出n个字符 [abcd]{3} 由abcd中的字母组成的任意长度为3的字符串
{n,} 指定至少n个匹配 [abcd]{3,} 由abcd中的字母组成的任意长度不小于3的字符串
{n,m} 指定至少n个但不多于m个匹配 [abcd]{3,5} 由abcd中的字母组成的任意长度不小于3但不大于5的字符串

代码示例

细节:java匹配默认贪婪匹配,即尽可能匹配多的字符串

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
// 限定符使用
public class Regex04 {
public static void main(String[] args) {
String content = "11111111aaaaaa2345";

String regStr1 = "a{3,4}"; // aaaa 而不会输出 aaa,aaa

String regStr2 = "1+"; // 11111111

String regStr2 = "\\d+"; // 11111111,2345

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

while (matcher.find()) {
System.out.println("find: " + matcher.group(0));
}

}
}

3.5 定位符

定位符的作用是规定要匹配的字符串出现的位置,比如在字符串的开始还是结束的位置。

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

代码演示

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
// 定位符使用
public class Regex05 {
public static void main(String[] args) {
String content = "a123abc";

String regStr = "^[0-9]+[a-z]*"; // null

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

while (matcher.find()) {
System.out.println("find: " + matcher.group(0));
}
}
}

3.6 分组 捕获 反向引用

  • 分组:我们可以用圆括号组成一个比较复杂的匹配模式,一个圆括号的部分就可以看作是一个子表达式
  • 捕获:把正则表达式中子表达式/分组匹配的内容,保存到内存中,以数字编号或显式命名的组里,方便后面引用。注意组0代表的是整个正则式,分组编号从1开始。
  • 反向引用:圆括号的内容被捕获后,可以在这个括号后被使用,从而写出一个比较实用的匹配模式。这种引用既可以在正则表达式内部,也可以在外部。内部反向引用使用\\分组号,外部引用使用$分组号

3.6.1 捕获分组

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

代码示例

  • 非命名捕获分组
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
// 分组
public class Regex06 {
public static void main(String[] args) {
String content = "Hongyi s7788 nn1198han";

String regStr = "(\\d\\d)(\\d\\d)";

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

while (matcher.find()) {
System.out.println("find: " + matcher.group(0));
System.out.println("第一个分组的内容: " + matcher.group(1));
System.out.println("第二个分组的内容: " + matcher.group(2));
}
}
}
1
2
3
4
5
6
find: 7788
第一个分组的内容: 77
第二个分组的内容: 88
find: 1198
第一个分组的内容: 11
第二个分组的内容: 98
  • 命名捕获分组
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
// 分组
public class Regex06 {
public static void main(String[] args) {
String content = "Hongyi s7788 nn1198han";

String regStr = "(?<g1>\\d\\d)(?<g2>\\d\\d)";

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

while (matcher.find()) {
System.out.println("find: " + matcher.group(0));
System.out.println("第一个分组的内容: " + matcher.group("g1"));
System.out.println("第二个分组的内容: " + matcher.group("g2"));
}
}
}

3.6.2 非捕获分组

  • (?:pattern):匹配pattern但不捕获该匹配的子表达式,即它是一个非捕获匹配,不存储供以后使用的匹配。这对于用or(|)组合模式部件的情况很有用。
    • industr(?:y|ies)是比industry|industries更经济的表达式
  • (?=pattern):非捕获匹配
    • Windows(?=95|98|NT|2000)匹配Windows 2000中的Windows,但不匹配Windows 3.1中的Windows
  • (?!pattern):非捕获匹配。匹配不处于匹配pattern的字符串的起始点的搜索字符串,即与第二条规则匹配相反的内容

代码演示

给定字符串:

1
hello韩顺平教育 jack韩顺平老师 韩顺平同学hello
  • 需求1:找到韩顺平教育、韩顺平老师、韩顺平同学三个字符串
1
2
3
4
5
6
7
8
9
10
11
12
13
14
public class Regex08 {
public static void main(String[] args) {
String content = "hello韩顺平教育 jack韩顺平老师 韩顺平同学hello";

String regStr = "韩顺平(?:教育|老师|同学)";

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

while (matcher.find()) {
System.out.println("find: " + matcher.group(0));
}
}
}
1
2
3
find: 韩顺平教育
find: 韩顺平老师
find: 韩顺平同学

注意:这里并没有分组,不能使用matcher.group(1)

  • 需求2:查找韩顺平关键字,但只找韩顺平教育和韩顺平老师含有的
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
// 非捕获分组
public class Regex08 {
public static void main(String[] args) {
String content = "hello韩顺平教育 jack韩顺平老师 韩顺平同学hello";

String regStr = "韩顺平(?=教育|老师)";

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

while (matcher.find()) {
System.out.println("find: " + matcher.group(0));
}
}
}
1
2
find: 韩顺平
find: 韩顺平
  • 需求3:查找韩顺平关键字,但不找韩顺平教育和韩顺平老师含有的
1
String regStr = "韩顺平(?!教育|老师)";
1
find: 韩顺平

3.6.3 反向引用

  • 需求:匹配两个连续的相同数字
1
2
String content = "hello jack11 tom22 Hongyi";
String regStr = "(\\d)\\1"; // 11 22
  • 需求:匹配四个连续的相同数字
1
2
String content = "hello jack1111 tom2222 Hongyi";
String regStr = "(\\d)\\1{4}"; // 1111 2222
  • 需求:匹配四位的回文数
1
2
String content = "hello jack1221 tom2332 Hongyi";
String regStr = "(\\d)(\\d)\\2\\1"; // 1221 2332
  • 需求:形如12319-333999111,前面是一个五位数,紧随连字符,然后是一个九位数,连续的三位要相同
1
String regStr = "\\d{5}-(\\d)\\1{2}(\\d)\\2{2}(\\d)\\3{2}";
  • 需求:结巴去重,把类似我...我要...学学学学...编程java!修改为我要学编程java!
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
public class Regex04 {
public static void main(String[] args) {
String content = "我...我要...学学学学...编程java!";
// 1.去掉所有的.
Pattern pattern = Pattern.compile("\\.");
Matcher matcher = pattern.matcher(content);
content = matcher.replaceAll("");

// 此时 content = "我我要学学学学编程java!"
// 2.去掉重复的字
// 使用 (.)\\1+ 匹配
pattern = Pattern.compile("(.)\\1+"); // 分组捕获的内容记录到$1
matcher = pattern.matcher(content);

content = matcher.replaceAll("$1");
System.out.println(content);
}
}

更简洁的方法(去掉.之后用一条语句完成):

1
content = Pattern.compile("(.)\\1+").matcher(content).replaceAll("$1");

3.7 非贪婪匹配

前面提到java的匹配默认是贪婪匹配,但?字符紧随其他任何限定符(* + ? {n} {n,} {n,m})之后时,转变为非贪婪匹配。

代码示例

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
public class Regex09 {
public static void main(String[] args) {
String content = "hello 1111 ok";

String regStr = "\\d+"; // 1111
String regStr1 = "\\d+?"; // 输出4个1

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

while (matcher.find()) {
System.out.println("find: " + matcher.group(0));
}
}
}

3.8 实践案例

3.8.1 汉字

需求:给定一段文字,检验文字是否全部为汉字。

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
public class Regex10 {
public static void main(String[] args) {
String content = "韩顺平教a育";
// \u0391-\uffe5为汉字的编码范围
String regStr = "^[\u0391-\uffe5]+$";

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

if (matcher.find()) {
System.out.println("满足格式");
} else {
System.out.println("不满足格式");
}
}
}
1
不满足格式

3.8.2 邮政编码

需求:验证是否为邮政编码

特点:1-9开头的六位数

1
String regStr = "^[1-9]\\d{5}$";

3.8.3 QQ号

需求:验证qq号

特点:1-9开头的一个(5-10位数)

1
String regStr = "^[1-9]\\d{4,9}$";

3.8.4 手机号码

需求:验证是否为手机号码

特点:必须以13,14,15,18开头的11位数

1
String regStr = "^[1](?:3|4|5|8)\\d{9}$";

3.8.5 URL

  • 需求:验证URL
  • 例如:https://www.bilibili.com/video/BV1Eq4y1E79W?p=17&spm_id_from=pageDriver
1
String regStr = "^((http|https)://)?([\\w-]+\\.)+[\\w-]+(\\/[\\w-?=&/%.#]*)?";

image-20220321211739945

4 常用类

java.util.regex包主要包括三个类:

  1. Pattern

  2. Matcher

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

4.1 Pattern类

pattern对象是一个正则表达式对象。Pattern类没有公共构造方法。要创造一个Pattern对象,需要调用其公共静态方法,返回一个Pattern类对象,该方法接收一个正则表达式作为它的第一个参数。

1
Pattern r = Pattern.conpile(pattern);

matches方法

用于整体匹配(从头开始检验),在验证输入的字符串是否满足条件时使用。

1
2
3
4
5
6
7
8
9
10
11
12
// 演示matchers方法
public class Regex01 {
public static void main(String[] args) {
String content = "hello abc hello, 韩顺平教育";

String regStr = "hello"; // false
String regStr1 = "hello.*"; // true

boolean matches = Pattern.matches(regStr1, content);
System.out.println("整体匹配=" + matches);
}
}

源码

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

4.2 Matcher类

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

常用方法

  • public int start():返回以前匹配的初始索引。

  • public int start(int group):返回在以前的匹配操作期间,由给定组所捕获的子序列的初始索引

  • public int end():返回最后匹配字符之后的偏移量。

  • public int end(int group):返回在以前的匹配操作期间,由给定组所捕获子序列的最后字符之后的偏移量。

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
public class Regex02 {
public static void main(String[] args) {
String content = "hello abc jack hello atm Hongyi";

String regStr = "hello";

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

while (matcher.find()) {
System.out.println("==============");
System.out.println(matcher.start());
System.out.println(matcher.end());
System.out.println("find: " + content.substring(matcher.start(), matcher.end()));
}
}
}
1
2
3
4
5
6
7
8
==============
0
5
find: hello
==============
15
20
find: hello
  • public boolean matches():尝试将整个区域与模式匹配(整体匹配)
1
2
3
4
5
6
7
8
9
10
11
12
13
public class Regex02 {
public static void main(String[] args) {
String content = "hello abc jack hello atm Hongyi";

String regStr = "hello"; // false
String regStr1 = "hello.*"; // true

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

System.out.println("整体匹配: " + matcher.matches());
}
}
  • public String replaceAll(String replacement):替换模式与给定替换字符串相匹配的输入序列的每个子序列。
1
2
3
4
5
6
7
8
9
10
11
12
13
14
public class Regex02 {
public static void main(String[] args) {
String content = "hello abc jack hello atm Hongyi";

String regStr = "hello";

Pattern pattern = Pattern.compile(regStr);
Matcher matcher = pattern.matcher(content);
// 注意原来的字符串content并没有改变
String newContent = matcher.replaceAll("world");
System.out.println(content);
System.out.println(newContent);
}
}
1
2
hello abc jack hello atm Hongyi
world abc jack world atm Hongyi