Java——正则表达式详解

吴声子夜歌 2024-08-16 10:05:04 阅读 95

目录

Java正则表达式1、正则表达式语法1.1、基本的元字符1.2、数量元字符1.3、位置元字符1.4、特殊字符元字符1.5、回溯引用和前后查找1.6、大小写转换1.7、匹配模式

2、Java中的正则表达式2.1、概述2.2、获取匹配位置2.3、捕获组

3、匹配单个字符3.1、匹配纯文本3.2、匹配任意字符3.3、匹配特殊字符

4、匹配一组字符4.1、匹配多个字符中的某一个4.2、利用字符集合区间4.3、取非匹配

5、使用元字符5.1、对特殊字符进行转义5.2、匹配空白字符5.3、匹配特定的字符类别

6、重复匹配6.1、有多少个匹配6.2、匹配的重复次数6.3、防止过度匹配

7、位置匹配7.1、单词边界7.2、字符串边界7.3、分行匹配模式

8、使用子表达式8.1、子表达式8.2、子表达式的嵌套

9、回溯引用9.1、回溯引用匹配9.2、回溯引用在替换操作中的应用9.3、大小写转换

10、前后查找10.1、向前查找10.2、向后查找10.3、向前查找和向后查找结合起来10.4、对前后查找取非

Java正则表达式

1、正则表达式语法

1.1、基本的元字符

元字符 说明
<code>. 匹配任意单个字符
<code>| 逻辑或操作符
<code>[] 匹配字符集合中的一个字符
<code>[^] 对字符集合求非
<code>- 定义一个区间(例如[A-Z])
<code>\ 对下一个字符转义

1.2、数量元字符

元字符 说明
<code>* 匹配前一个字符(子表达式)的零次或多次重复(默认贪婪匹配)
<code>*? *的懒惰匹配版本
<code>+ 匹配前一个字符(子表达式)的一次或多次重复(默认贪婪匹配)
<code>+? +的懒惰匹配版本
<code>? 匹配前一个字符(子表达式)的零次或一次重复
<code>{n} 匹配前一个字符(子表达式)的n次重复
<code>{m, n} 匹配前一个字符(子表达式)至少m次且至多n次重复
<code>{n, } 匹配前一个字符(子表达式)n次或更多次重复
<code>{n, }? {n, }的懒惰匹配版本

1.3、位置元字符

元字符 说明
<code>^ 匹配字符串的开头
<code>\A 匹配字符串的开头
<code>$ 匹配字符串的结束
<code>\Z 匹配字符串的结束
<code>\< 匹配单词的开头
<code>\> 匹配单词的结束
<code>\b 匹配单词边界(开头和结束)
<code>\B \b的反义

1.4、特殊字符元字符

元字符 说明
<code>[\b] 退格字符
<code>\c 匹配一个控制字符
<code>\d 匹配任意数字字符
<code>\D \d的反义
<code>\f 换页符
<code>\n 换行符
<code>\r 回车符
<code>\s 匹配一个空白字符
<code>\t 制表符(Tab字符)
<code>\v 垂直制表符
<code>\w 匹配任意字母数字字符或下划线字符
<code>\W \w的反义
<code>\x 匹配一个十六进制数字
<code>\0 匹配一个八进制数字

1.5、回溯引用和前后查找

元字符 说明
<code>() 定义一个子表达式
<code>\1 匹配第1个子表达式:\2表示第2个子表达式,以此类推
<code>?= 向前查找
<code>?<= 向后查找
<code>?! 负向前查找
<code>?<! 负向后查找
<code>?() 条件(if then)
<code>?()| 条件(if then else)

1.6、大小写转换

元字符 说明
<code>\E 结束\L或\U转换
<code>\l 把下一个字符转换位小写
<code>\L 把后面的字符转换位小写,直到遇见\E为止
<code>\u 把下一个字符转换位大写
<code>\U 把后面的字符转换位大写,直到遇到\E为止

1.7、匹配模式

元字符 说明
<code>(?m) 分行匹配模式
<code>(?i) 忽略大小写
<code>(?d) Unix行模式
<code>(?x) 注释模式
<code>(?s) dotall模式
<code>(?u) 将以与Unicode标准一致的方式进行大小写不敏感匹配

2、Java中的正则表达式

2.1、概述

Java对正则表达式的支持是从1.4版本开始的,此前的JRE版本不支持正则表达式。

Java语言中的正则表达式匹配功能主要是通过java.util.regex.Matcherjava.util.regex.Pattern类实现的。

Matcher类提供如下几个常用方法:

find():在一个字符串里寻找一个给定模式的匹配lookingAt():用一个给定的模式尝试匹配一个字符串的开头matches():用一个给定的模式去尝试匹配一个完整的字符串replaceAll():进行替换操作,对所有的匹配都进行替换replaceFirst():进行替换操作,只对第一个匹配进行替换

Pattern类提供如下几个常用方法:

compile():把一个正则表达式编译成一个模式flags():返回某给定模式的匹配标志matches():在功能上等价于maches()方法pattern():把一个模式还原位一个正则表达式split():把一个字符串拆分位子字符串

Java正则表达式支持与Perl语言基本兼容,但要注意以下几点:

不支持嵌入条件不支持使用\E\l\L\u\U进行字母大小写转换不支持使用\b匹配退格符不支持\z

2.2、获取匹配位置

Matcher类提供了如下方法以获取匹配的位置:

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

String content = "hello edu jack tom hello smith hello fff";

String reg = "hello";

Pattern pattern = Pattern.compile(reg);

Matcher matcher = pattern.matcher(content);

StringBuffer buffer = new StringBuffer();

while (matcher.find()) {

System.out.println("=============");

System.out.println(matcher.start());

System.out.println(matcher.end());

matcher.appendReplacement(buffer, "###");

}

matcher.appendTail(buffer);

System.out.println(buffer.toString());

// =============

// 0

// 5

// =============

// 19

// 24

// =============

// 31

// 36

// ### edu jack tom ### smith ### fff

2.3、捕获组

非命名捕获 (pattern)

捕获匹配的子字符串。编号为零的第一个捕获是由整个正则表达式模式匹配的文本,其它捕获结果则根据左括号的顺序从1开始自动编号。

String content = "hanshunping s7789 nn1189han";

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));

}

// 7789

// 77

// 89

// 1189

// 11

// 89

命名捕获 (?<name>pattern)

将匹配的子字符串捕获到一个组名称或编号名称中。用于name的字符串不能包含任何符号,并且不能以数字开头。可以使用单引号代替尖括号。例如:(?'name')

String content = "hanshunping s7789 nn1189han";

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"));

}

// 7789

// 77

// 89

// 1189

// 11

// 89

非捕获匹配 (?:pattern)

匹配pattern但不捕获该匹配的子表达式,即它是一个非捕获匹配,不存储以后使用的匹配。这对于用"or"字符(|)组合模式部件的情况很有用。例如,'industr(?:y|ies)'是比’industry|industries’更经济的表达式。

String content = "hello韩顺平教育 jack韩顺平老师 韩顺平同学hello";

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

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));//错误,并不会捕获分组

}

// 韩顺平教育

// 韩顺平老师

// 韩顺平同学

非捕获匹配 (?=pattern)

它是一个非捕获匹配。例如,'Windows(?=95|98|NT|2000)'匹配"Windows 2000"中的"Windows",但是不匹配"Windows 3.1"中的"Windows"。

String content = "Windows2000 Windows3.1";

String regStr = "Windows(?=95|98|NT|2000)";

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));//错误,并不会捕获分组

}

String res = matcher.replaceAll("@@@@@");

System.out.println(res);

// Windows

// @@@@@2000 Windows3.1

非捕获匹配 (?!pattern)

该表达式匹配不处于匹配pattern的字符串的起始点的搜索字符串。它是一个非捕获匹配。例如,'Windows (?!95|98|NT|2000)'匹配"Windows 3.1"中的"Windows",但是不匹配"Windows 2000"中的"Windows"。

String content = "Windows2000 Windows3.1";

String regStr = "Windows(?!95|98|NT|2000)";

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));//错误,并不会捕获分组

}

String res = matcher.replaceAll("@@@@@");

System.out.println(res);

// Windows

// Windows2000 @@@@@3.1

3、匹配单个字符

3.1、匹配纯文本

有多个匹配结果,使用全局匹配:

在这里插入图片描述

字母大小写,使用i标志忽略大小写:

在这里插入图片描述

<code>String str = "Hello, my name is Ben. \n" +

"Please visit my website at http:www.baidu.com.";

//全局匹配

String pattern = "my";

Pattern r = Pattern.compile(pattern);

Matcher m = r.matcher(str);

while (m.find()) {

System.out.println(m.group());

}

//忽略大小写

pattern = "h";

r = Pattern.compile(pattern, Pattern.CASE_INSENSITIVE);

m = r.matcher(str);

while (m.find()) {

System.out.println(m.group());

}

3.2、匹配任意字符

使用.匹配任意字符:

在这里插入图片描述

3.3、匹配特殊字符

使用<code>\转义字符:

在这里插入图片描述

4、匹配一组字符

4.1、匹配多个字符中的某一个

在这里插入图片描述

4.2、利用字符集合区间

字符区间并不仅限于数字,以下这些都是合法的字符区间:

<code>A-Z,匹配从A到Z的所有大写字母。a-z,匹配从a到z的所有小写字母。A-F,匹配从A到F的所有大写字母。A-z,匹配从ASCIⅡ字符A到ASCI字符z的所有字母。这个模式一般不常用,因为它还包含着[和等在ASCI字符表里排列在Z和a之间的字符。

在这里插入图片描述

4.3、取非匹配

集合中使用<code>^:

在这里插入图片描述

5、使用元字符

5.1、对特殊字符进行转义

在这里插入图片描述

5.2、匹配空白字符

元字符 说明
<code>[\b] 回退(并删除)一个字符(Backsace键)
<code>\f 换页符
<code>\n 换行符
<code>\r 回车符
<code>\t 制表符(Tab键)
<code>\v 垂直制表符

在这里插入图片描述

5.3、匹配特定的字符类别

元字符 说明
<code>\d 任何一个数字字符(等价于<code>[0-9])
<code>\D 任何一个非数字字符(等价于<code>[^0-9])

在这里插入图片描述

在这里插入图片描述

元字符 说明
<code>\w 任何一个字母数字字符(大小写均可)或下划线字符(等价于<code>[a-zA-Z0-9_])
<code>\W 任何一个非字母数字或非下划线字符(等价于<code>[^a-zA-Z0-9_])

在这里插入图片描述

元字符 说明
<code>\s 任何一个空白字符(等价于<code>[\f\n\r\t\v])
<code>\S 任何一个非空白字符(等价于<code>[^\f\n\r\t\v])

在这里插入图片描述

匹配十六进制(<code>\x前缀)或八进制数值(\0前缀):

在这里插入图片描述

<code>\x0A对应于ASCII字符10(换行符),其效果等价于\n

6、重复匹配

6.1、有多少个匹配

使用+,匹配一个或多个字符:

在这里插入图片描述

使用<code>*,匹配零个或多个字符:

在这里插入图片描述

使用<code>?,匹配零个或一个字符:

在这里插入图片描述

6.2、匹配的重复次数

使用<code>{n},位重复匹配次数设定一个精确的值:

在这里插入图片描述

使用<code>{m, n},位重复匹配次数设定一个区间:

在这里插入图片描述

使用<code>{m,},匹配"至少重复多少次":

在这里插入图片描述

6.3、防止过度匹配

贪婪型元字符 懒惰型元字符
<code>* <code>*?
<code>+ <code>+?
<code>{n,} <code>{n,}?

贪婪匹配的结果,将第一个B标签的开头和第二个B标签的结尾匹配为一对:

在这里插入图片描述

懒惰匹配的结果:

在这里插入图片描述

<code>String str = "This offer is not available to customers living in <B>AK</B> and <B>HI</B>.";

//贪婪匹配

String pattern = "<[Bb]>.*</[Bb]>";

Pattern r = Pattern.compile(pattern);

Matcher m = r.matcher(str);

while (m.find()) {

System.out.println(m.group());

}

//懒惰匹配

pattern = "<[Bb]>.*?</[Bb]>";

r = Pattern.compile(pattern);

m =r.matcher(str);

while (m.find()) {

System.out.println(m.group());

}

7、位置匹配

7.1、单词边界

使用\b来匹配一个单词的开始或结尾:

在这里插入图片描述

使用<code>\B表明不匹配一个单词边界:

在这里插入图片描述

7.2、字符串边界

使用<code>^匹配字符串的开头,$匹配字符串的结尾:

在这里插入图片描述

7.3、分行匹配模式

使用<code>(?m)启用分行匹配模式:

在这里插入图片描述

<code>String str = "aaa\n" +

"aba\n" +

"aca\n" +

"bab\n" +

"cac";

String pattern = "(?m)^a.*a$";

Pattern r = Pattern.compile(pattern);

Matcher m = r.matcher(str);

while (m.find()) {

System.out.println(m.group());

}

8、使用子表达式

8.1、子表达式

子表达式是一个更大的表达式的一部分;把一个表达式划分为一系列子表达式的目的是为了把那些子表达式当作一个独立元素来使用。子表达式必须用()括起来。

在这里插入图片描述

8.2、子表达式的嵌套

<code>String str = "Pinging hog.forta.com [12.159.46.200] with 32 bytes of data:";

String pattern = "(((\\d{1,2})|(1\\d{2})|(2[0-4]\\d)|(25[0-5]))\\.){3}((\\d{1,2})|(1\\d{2})|(2[0-4]\\d)|(25[0-5]))";

Pattern r = Pattern.compile(pattern);

Matcher m = r.matcher(str);

while (m.find()) {

System.out.println(m.group());

}

9、回溯引用

9.1、回溯引用匹配

使用\n来引用模式里的子表达式(\1表示第一个子表达式,\2表示第二个子表达式;依次类推):

在这里插入图片描述

<code>String str = "This is a block of of text,\n" +

"several words here are are\n" +

"repeated, and and they\n" +

"should not be.";

String pattern = "[ ]+(\\w+)[ ]\\1";

Pattern r = Pattern.compile(pattern);

Matcher m = r.matcher(str);

while (m.find()) {

System.out.println(m.group());

}

9.2、回溯引用在替换操作中的应用

替换操作需要用到两个正则表达式:一个用来给出搜索模式,另一个用来给出匹配文本的替换模式。回溯引用可以跨模式使用,在第一个模式里被匹配的子表达式可以用在第二个模式里。

在这里插入图片描述

匹配到了邮箱地址,先把邮箱地址放在A标签中:

<code># 替换,$1引用子表达式

<a herf="mailto:$1">$1</a>code>

结果:

Hello, <a herf="mailto:ben@forta.com">ben@forta.com</a> is my email address.code>

Java实现:

String str = "Hello, ben@forta.com is my email address.";

String pattern = "(\\w+[\\w\\.]*@[\\w\\.]+\\.\\w+)";

Pattern r = Pattern.compile(pattern);

Matcher m = r.matcher(str);

String res = m.replaceAll("<a herf=\"mailto:$1\">$1</a>");

System.out.println(res);//Hello, <a herf="mailto:ben@forta.com">ben@forta.com</a> is my email address.code>

9.3、大小写转换

元字符 说明
<code>\E 结束\L或\U转换
<code>\l 把下一个字符转换位小写
<code>\L 把后面的字符转换位小写,直到遇见\E为止
<code>\u 把下一个字符转换位大写
<code>\U 把后面的字符转换位大写,直到遇到\E为止

把一级标题中的文本转换为大写:

在这里插入图片描述

替换:

<code>$1\U$2\E$3

结果:

<BODY>

<H1>WELCOME TO MY HOMEPAGE</H1>

<H2>Coldfusion</H2>

<H2>Wireless</H2>

<H2>This is not valid HTML</H3>

</BODY>

注意:Java并不支持此种大小写转换!

10、前后查找

10.1、向前查找

向前查找指定了一个必须匹配但不在结果中返回的模式。向前查找实际就是一个子表达式,而且从格式上看也确实如此。从语法上看,一个向前查找模式其实就是一个以?=开头的子表达式,需要匹配的文本跟在=的后面。

在这里插入图片描述

在上面列出的URL地址里,协议名与主机名之间以一个<code>:分隔。模式.+匹配任意文本(第1个匹配是http),子表达式(?=:)匹配。注意,被匹配到的并没有出现在最终的匹配结果里;我们用?=向正则表达式引擎表明:只要找到就行了,不要把它包括在最终的匹配结果里,用术语来说,就是“不消费”它。

String str = "http://www.forta.com/\n" +

"https://mail.forta.com/\n" +

"ftp://ftp.forta.com/";

String pattern = ".+(?=:)";

Pattern r = Pattern.compile(pattern);

Matcher m = r.matcher(str);

while (m.find()) {

System.out.println(m.group());

}

10.2、向后查找

正如你刚看到的那样,?=将向前查找(查找出现在被匹配文本之后的字符,但不消费那个字符)。因此,?=被称为向前查找操作符。除了向前查找,许多正则表达式实现还支持向后查找,也就是查找出现在被匹配文本之前的字符(但不消费它),向后查找操作符是?<=

?<=?=的具体使用方法大同小异;它必须用在一个子表达式里,而且后跟要匹配的文本。

String str = "ABC01: $23.45\n" +

"HGG42: $5.31\n" +

"CFMX1: $899.00\n" +

"XTC99: $68.96\n" +

"Total items found: 4";

String pattern = "(?<=\\$)[0-9.]+";

Pattern r = Pattern.compile(pattern);

Matcher m = r.matcher(str);

while (m.find()) {

System.out.println(m.group());

}

// 23.45

// 5.31

// 899.00

// 68.96

(?<=\$)只匹配$,但不消费它;最终的匹配结果里只有价格数字,没有前缀$字符。

10.3、向前查找和向后查找结合起来

String str = "<HEAD>\n" +

"<TITLE>Ben Forta's Homepage</TITLE>\n" +

"</HEAD>";

String pattern = "(?<=<[tT][iI][tT][lL][eE]>).*(?=</[tT][iI][tT][lL][eE]>)";

Pattern r = Pattern.compile(pattern);

Matcher m = r.matcher(str);

while (m.find()) {

System.out.println(m.group());

//Ben Forta's Homepage

}

10.4、对前后查找取非

前后查找还有一种不太常见的用法叫作负前后查找(negative lookaround)。负向前查找(negative lookahead)将向前查找不与给定模式相匹配的文本,负向后查找(negative lookbehind)将向后查找不与给定模式相匹配的文本。

操作符 说明
<code>(?=) 正向前查找
<code>(?!) 负向前查找
<code>(?<=) 正向后查找
<code>(?<!) 负向后查找

<code>String str = "I paid $30 for 100 apples,\n" +

"50 oranges, and 60 pears.\n" +

"I saved $5 on this order.";

//只查找数量,负向后查找,匹配结果只包含那些不以$开头的数值

String pattern = "\\b(?<!\\$)\\d+\\b";

Pattern r = Pattern.compile(pattern);

Matcher m = r.matcher(str);

while (m.find()) {

System.out.println(m.group());

}



声明

本文内容仅代表作者观点,或转载于其他网站,本站不以此文作为商业用途
如有涉及侵权,请联系本站进行删除
转载本站原创文章,请注明来源及作者。