Java 正则表达式

本文最后更新于:2 分钟前

本文根据 B站视频整理,链接如下

简介

正则表达式是对字符串执行模式匹配的技术

正则表达式:regular expression 简称: RegExp

正则表达式的语法

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

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

元字符

转义号 \\

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

再次提示:

在Java的正则表达式中,两个 \\ 代表其他语言中的一个 /

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

字符匹配符

符号符号实例解释匹配输入
[ ]可接收的字符列表[ajkh]a、j、k、h、中任意一个字符q
[ ^ ]不接收的字符列表[^abc]除a、b、c、之外的任意一个字符,包含数字和特殊符号d
-连字符a-z任意单个小写字母d
.匹配除\n以外的任何字符a..b以a开头,以b结尾,中间包括2个任意字符的长度为4的字符串aaab
\\d匹配单个数字字符,相当于[0-9]\\d{3}(\\d)?包括3个或4个数字的字符串123、2313
\\D匹配单个非数字字符,相当于[^0-9]\\D(\\d)*以单个非数字字符开头,后接任意个数字符串a、A24
\\w匹配单个数字、大小写字母字符、下划线,相当于[0-9a-zA-Z_]\\d{3}\\w{4}以3个数字字符开头的长度为7的数字字母字符串123sad2
\\W匹配单个非数字、大小写字母字符,相当于[^0-9a-zA-Z_]\\W+\\d{2}以至少1个非数字字母字符开头,2个数字字符结尾的字符串#28、@sdfq12

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

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

例子:

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 Regexp02 {
public static void main(String[] args) {
String content = "a1.1c8s.abc ABC_@";

// String regexp = "[a-z]"; // 匹配 a-z 之间的任意一个字符
// String regexp = "abc"; // 匹配abc,默认区分大小写
// String regexp = "(?i)abc"; // 匹配abc,不区分大小写
// a(?i)bc 表示bc不区分大小写 a((?i)b)c 表示 只有b不区分大小写

// String regexp = "[^a-z]"; // 匹配不在 a-z 之间任意1个字符
// String regexp = "[^a-z]{2}"; // 匹配不在 a-z 之间任意2个字符
// String regexp = "[abcd]"; // 匹配abcd 中的任意字符
// String regexp = "[^abcd]"; // 匹配不是abcd 中的任意字符
// String regexp = "\\d"; // 相当于 [0-9]
// String regexp = "\\D"; // 相当于 [^0-9]
// String regexp = "\\w"; // 匹配的是任意英文字母,数字和下划线,相当于 [0-9a-zA-Z_]
// String regexp = "\\W"; // 相当于 [^0-9a-zA-Z_]
// String regexp = "\\s"; // 匹配任何空白字符(空格,制表符)
// String regexp = "\\S"; // 匹配任何非空白字符
// String regexp = "\\."; // 匹配 .
String regexp = "."; // 匹配出 \n 之外的所有字符,如果要匹配.本身,需要使用 \\.


Pattern pattern = Pattern.compile(regexp);
// Pattern pattern = Pattern.compile(regexp, Pattern.CASE_INSENSITIVE); // 这样也表示不区分大小写
Matcher matcher = pattern.matcher(content);
while (matcher.find()) {
System.out.print("结果:" + matcher.group(0) + " ");
}
}
}

选择匹配符

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

符号符号示例解释
|匹配 “|” 之前或之后的表达式ab|cdab或者cd

例子:

1
2
3
4
5
6
7
8
9
10
11
12
public class Regexp03 {
public static void main(String[] args) {

String content = "sdfhqwhqwe12411";
String regexp = "sd|24";
Pattern pattern = Pattern.compile(regexp);
Matcher matcher = pattern.matcher(content);
while (matcher.find()) {
System.out.println("结果是:" + matcher.group(0));
}
}
}

限定符

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

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

例子:

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
public class Regexp04 {
public static void main(String[] args) {

String content = "411111111a1aaaaahello,world";

// String regexp = "a{3}"; // 表示匹配 aaa
// String regexp = "1{4}"; // 表示匹配 1111
// String regexp = "\\d{2}"; // 表示匹配 2位的任意数字字符


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


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

// *
// String regexp = "1*"; // 表示匹配 0个1或者多个1

// ?
String regexp = "a1?"; // 表示 匹配 a 或者a1

Pattern pattern = Pattern.compile(regexp);
Matcher matcher = pattern.matcher(content);
while (matcher.find()) {
System.out.println("结果--> " + matcher.group(0));
}
}
}

定位符

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

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

例子:

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
public class Regexp05 {
public static void main(String[] args) {
// String content = "123-abc";
String content = "hellojinanc aahello";

// String regexp = "^[0-9]+[a-z]*"; // 以至少一个数字开头,后接任意个小写字母的字符串
// String regexp = "^[0-9]+[a-z]+$"; // 以至少一个数字开头,必须以至少一个小写字母结尾
// String regexp = "^[0-9]+\\-[a-z]+$"; // 以至少一个数字开头,中间有个 - ,必须以至少一个小写字母结尾

// String regexp = "hello\\b"; // 匹配边界的 hello【这里的边界可以 是字符串的最后,也可以是子字符串的后面】
String regexp = "hello\\B"; // 和 \\b 的含义相反

Pattern pattern = Pattern.compile(regexp);
Matcher matcher = pattern.matcher(content);
while (matcher.find()) {
System.out.println(matcher.group(0));
}
}
}

分组

常用分组

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

例子:

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
public class Regexp06 {
public static void main(String[] args) {

String content = "helloworld hello 98763432";
// 下面就是 非命名分组
// 说明 通过 group(0) 得到匹配的字符串
// 说明 通过 group(1) 得到匹配的字符串的第1个分组的内容
// 说明 通过 group(2) 得到匹配的字符串的第2个分组的内容
// String regexp = "(\\d\\d)(\\d\\d)"; //匹配4个数字

// 命名分组 即可以给分组取名

String regexp = "(?<c1>\\d\\d)(?<c2>\\d\\d)"; //匹配4个数字
Pattern pattern = Pattern.compile(regexp);
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("c1"));
System.out.println("第二组:" + matcher.group(2));
System.out.println("通过分组名称取值:" + matcher.group("c2"));
}
}
}

特别分组

常用分组构造形式说明
(?:pattern)匹配 pattern但不捕获该匹配的子表达式,即它是一个非捕获匹配,不存储供以后使用的匹配。这对于用”or”字符(组合模式部件的情况很有用。例如,”industr(?:y[lies)是比’industrylindustries’更经济的表达式。
(?=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”。

例子:

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
public class Regexp07 {
public static void main(String[] args) {
String content = "hello仅安小课堂 Jack仅安老师 仅安同学hello";

/**
* 使用 非捕获分组完成下列问题
* 1、找到 仅安 仅安老师 仅安同学 子字符串
* 2、找到 仅安 这个关键字 ,但是要求只是查找 仅安小课堂和仅安老师中包含的仅安
* 3、找到 仅安这个关键字,但是要求只是查找 不是 (仅安小课堂和仅安老师)中包含有的仅安
*/

// 第一题
// String regexp = "仅安小课堂|仅安老师|仅安同学";
// 上面的写法可以等价 非捕获分组 注意: 不能 matcher.group(1)
// String regexp = "仅安(?:小课堂|老师|同学)";

// 第二题
// String regexp = "仅安(?=小课堂|老师)";

// 第三题
String regexp = "仅安(?!小课堂|老师)";

Pattern pattern = Pattern.compile(regexp);
Matcher matcher = pattern.matcher(content);
while (matcher.find()) {
System.out.println(matcher.group(0));
}
}
}

应用实例

例子:

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
public class Regexp09 {
public static void main(String[] args) {
/**
* 1、是不是汉字
* 2、是不是邮政编码 要求:(是1-9开头的一个六位数,例如:276200)
* 3、QQ号码 要求:是一个1-9开头的(5位数到10位数) 比如:21341,3412521512
* 4、手机号码:要求:必须以 13、14、15、18开头的11位数,比如 13210628888
*/

// 第一题
// String content = "这里是仅安";
// String regexp = "^[\u0391-\uffe5]+$";

//第二题
// String content = "276200";
// String regexp = "^[1-9]\\d{5}$";

//第三题
// String content = "3314712324";
// String regexp = "^[1-9]\\d{4,9}$";

// 第四题
String content = "15147123242";
String regexp = "^1[3|4|5|8]\\d{9}$";

Pattern pattern = Pattern.compile(regexp);
Matcher matcher = pattern.matcher(content);
if (matcher.find()) {
System.out.println("满足条件,结果为: " + matcher.group(0));
} else {
System.out.println("不满足条件");
}
}
}

验证 URL,较为复杂

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
public class Regexp10 {
public static void main(String[] args) {

String content = "https://www.bilibili.com/video/BV1Eq4y1E79W?p=17&spm_id_from=pageDriver";


/**
* 思路
* 1、先确定 URL 的开始部分 https:// | http:// ^((https|http)://)
* 2、通过 ([\w-]+\.)+[\w-]+ 来 匹配 www.bilibili.com
* 3、 /video/BV1Eq4y1E79W?p=17&spm_id_from=pageDriver 如何匹配呢?
* [\w-]+(\/[\w-?=%&#/.]*)?
*/

// [.] 代表 匹配 .本身,等价于 \\.
String regexp = "^((https|http)://)?([\\w-]+\\.)+[\\w-]+(\\/[\\w-?=%&#/.]*)?$";

Pattern pattern = Pattern.compile(regexp);
Matcher matcher = pattern.matcher(content);
if (matcher.find()) {
System.out.println("满足条件,结果为: " + matcher.group(0));
} else {
System.out.println("不满足条件");
}
}
}

三个常用类

Pattern 、Matcher、PatternSyntaxException

Pattern类

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

1
2
3
4
5
6
7
8
9
public class PatternMethod {
public static void main(String[] args) {
String content = "hello hello,world,仅安";
// String regexp = "hello"; //false
String regexp = "hello.*";
boolean matches = Pattern.matches(regexp, content);
System.out.println("整体匹配= " + matches);
}
}

Matcher 类

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

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
public class MatcherMethod {
public static void main(String[] args) {
String content = "hello jinanc world hello jinanc apple xiaomi";

String regexp = "hello";

Pattern pattern = Pattern.compile(regexp);
Matcher matcher = pattern.matcher(content);
while (matcher.find()) {
System.out.println("========");
System.out.println(matcher.start());
System.out.println(matcher.end());
System.out.println("找到:" + content.substring(matcher.start(), matcher.end()));
}

//整体匹配方法,常用于某个字符串是否满足某个规则
System.out.println("整体匹配:" + matcher.matches());

// 把 jinanc 替换为 仅安
regexp = "jinanc";
pattern = Pattern.compile(regexp);
matcher = pattern.matcher(content);
// 返回后的 字符串才是 替换后的字符 原来的content 不变化
String newContent = matcher.replaceAll("仅安");
System.out.println("newContent:" + newContent);
}

PatternSyntaxException

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

分组、捕获、反向引用

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

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

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

小案例:

  • 要匹配两个连续的相同数字
  • 要匹配五个连续的相同数字
  • 要匹配个位和千位相同,十位和百位相同的数

思考题:

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

代码实现

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
public class Regexp11 {
public static void main(String[] args) {
String content = "hello tom11 hhh22 tom jack33333 ce1331 12321-333999111";
// 要匹配2个连续的相同数字
// String regexp = "(\\d)\\1";

// 要匹配5个连续的相同数字
// String regexp = "(\\d)\\1{4}";

//要匹配个位和千位相同,十位和百位相同的数
// String regexp = "(\\d)(\\d)\\2\\1";

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


Pattern pattern = Pattern.compile(regexp);
Matcher matcher = pattern.matcher(content);
while (matcher.find()) {
System.out.println(matcher.group(0));
}
}
}

结巴去除案例

把类似:”我….我要….学学学学….编程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
public class Regexp12 {
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);

// 2、去掉重复的字 我我要学学学学编程java!
// 使用 (.)\\1+

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

// 使用一条语句来解决
content = Pattern.compile("(.)\\1+").matcher(content).replaceAll("$1");
System.out.println(content);
}
}

替换分割匹配

在 String类中使用正则表达式

替换功能

1
public String replaceAll(String regex, String replacement)

例子:

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

// 将 JDK1.3 JDK1.4 替换成为JDK
content = content.replaceAll("JDK1\\.[3|4]", "JDK");
System.out.println(content);

是否满足某个条件

1
public boolean matches(String regex)

例子:

1
2
3
4
5
6
7
// 验证一个手机号是不是138开头
String phone = "13826789291";
if (phone.matches("1(38|39])\\d{8}")) {
System.out.println("验证成功");
} else {
System.out.println("验证失败");
}

分割功能

1
public String[] split(String regex)

例子:

1
2
3
4
5
6
// 要求按照#或者-或者~或者数字来分割
String str = "hello#ttabc-jack12smith~北京";
String[] split = str.split("#|-|\\d+|~");
for (String s : split) {
System.out.println(s);
}

几个案例

案例一:

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
public class work01 {
public static void main(String[] args) {
//规定电子邮件规则为//只能有一个@
//@前面是用户名,可以是a-z A-Z 0-9_-字符
//@后面是域名,并且域名只能是英文字母,比如 sohu. com或者tsinghua.org.cnl /
// 写出对应的正则表达式,验证输入的字符串是否为满足规则

String content = "2640053707@qq.com";
String regexp = "[\\w-]+@([a-zA-Z]+\\.)+[a-zA-Z]+";
if (content.matches(regexp)) {
System.out.println("匹配成功");
} else {
System.out.println("匹配失败");
}
}
}

案例二:

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
public class work02 {
public static void main(String[] args) {
// '要求验证是不是整数或者小数
// 提示:这个题要考虑正数和负数
//比如:123 -345 34.89 -87.9 -0.01 0.45等

String content = "-0.1313";
String regexp = "^[-+]?([1-9]\\d*|0)(\\.\\d+)?$";
if (content.matches(regexp)) {
System.out.println("匹配成功");
} else {
System.out.println("匹配失败");
}
}
}

案例三:

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
public class work03 {
public static void main(String[] args) {
// http://www.sohu.com:8080/abc/index.htm
// 1、要求得到协议是什么? http
// 2、域名是什么? www.sohu.com
// 3、端口是什么? 8080
// 4、文件名是什么? index.htm
String content = "https://www.sohu.com:8080/abc/index.htm";
String regexp = "^([a-zA-Z]+)://([a-zA-Z.]+):(\\d+)[\\w-/]*/([\\w.]+)$";
Pattern pattern = Pattern.compile(regexp);
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.err.println("匹配失败了...");
}
}
}

一些常用的正则参考

菜鸟工具


本文作者: 仅安
本文链接: https://jinan6.vip/posts/230353743/
版权声明: 本博客所有文章除特别声明外,均采用 CC BY-SA 4.0 协议 ,转载请注明出处!