Java 中通过正则表达式可以实现对字符串的有效匹配和处理。本篇文章将详细介绍 Java 中的正则表达式,包括基本概念、语法规则、常用方法等。
1 基本概念
1 正则表达式是什么?
正则表达式(Regular Expression),又称正规表示法,常简称为正则表达式、正则式或规则(Reading),其实是一种字符串匹配的工具,它可以用来描述字符序列的模式。
2 正则表达式的作用
在 Java 中,正则表达式主要用于以下两个方面:
- 文本内容匹配:根据规则匹配指定的字符串。
- 文本内容替换:根据规则将指定字符串中符合条件的部分替换为新的内容。
2 语法规则
2.1.基本的元字符
元字符 | 说明 |
---|---|
. | 匹配任意单个字符 |
[] | 匹配字符集合中的一个字符 |
[^] | 对字符集合求非 |
:- | 定义一个区间(例如[A-Z]) |
\ | 对下一个字符转义 |
2.2 数量元字符
元字符 | 说明 |
---|---|
* | 匹配前一个字符(子表达式)的零次或多次重复(默认贪婪匹配) |
*? | *的懒惰匹配版本 |
+ | 匹配前一个字符(子表达式)的一次或多次重复(默认贪婪匹配) |
+? | +的懒惰匹配版本 |
? | 匹配前一个字符(子表达式)的零次或一次重复 |
{n} | 匹配前一个字符(子表达式)的n次重复 |
{m, n} | 匹配前一个字符(子表达式)至少m次且至多n次重复 |
{n, } | 匹配前一个字符(子表达式)n次或更多次重复 |
{n, }? | {n, }的懒惰匹配版本 |
补充:
表达式 .* 的意思很好理解,就是单个字符匹配任意次,即贪婪匹配。【先匹配到{age}之后,贪婪的继续匹配后面的字符,最终匹配到{age} years old,Bob is {age}】
表达式 .*? 是满足条件的情况只匹配一次,即懒惰匹配!【先匹配到{age}之后,懒惰不在拼接继续匹配后面的,而是开启新的匹配,最终匹配到了连个{age}】
2.3 位置元字符
元字符 | 说明 |
---|---|
^ | 匹配字符串的开头 |
\A | 匹配字符串的开头 |
$ | 匹配字符串的结束 |
\Z | 匹配字符串的结束 |
< | 匹配单词的开头 |
> | 匹配单词的结束 |
\b | 匹配单词边界(开头和结束) |
\B | \b的反义 |
2.4 特殊字符元字符
元字符 | 说明 |
---|---|
[\b] | 退格字符 |
\c | 匹配一个控制字符 |
\d | 匹配任意数字字符 |
\D | \d的反义 |
\f | 换页符 |
\n | 换行符 |
\r | 回车符 |
\s | 匹配一个空白字符 |
\t | 制表符(Tab字符) |
\v | 垂直制表符 |
\w | 匹配任意字母数字字符或下划线字符 |
\W | \w的反义 |
\x | 匹配一个十六进制数字 |
\0 | 匹配一个八进制数字 |
2.5 回溯引用和前后查找
元字符 | 说明 |
---|---|
() | 定义一个子表达式 |
\1 | 匹配第2.子表达式:\2表示第2个子表达式,以此类推 |
?= | 向前查找 |
?<= | 向后查找 |
?! | 负向前查找 |
?<! | 负向后查找 |
?() | 条件(if then) |
?() |
2.6 大小写转换
元字符 | 说明 |
---|---|
\E | 结束\L或\U转换 |
\l | 把下一个字符转换位小写 |
\L | 把后面的字符转换位小写,直到遇见\E为止 |
\u | 把下一个字符转换位大写 |
\U | 把后面的字符转换位大写,直到遇到\E为止 |
2.7匹配模式
元字符 | 说明 |
---|---|
(?m) | 分行匹配模式 |
(?i) | 忽略大小写 |
(?d) | Unix行模式 |
(?x) | 注释模式 |
(?s) | dotall模式 |
(?u) | 将以与Unicode标准一致的方式进行大小写不敏感匹配 |
例如:
忽视大消息分行匹配
3 Java中的正则表达式
3.1 概述
Java对正则表达式的支持是从1.4版本开始的,此前的JRE版本不支持正则表达式。
Java语言中的正则表达式匹配功能主要是通过java.util.regex.Matcher和java.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
3.2 获取匹配位置(匹配到替换)
Matcher类提供了如下方法以获取匹配的位置:
- public int start():返回以前匹配的初始索引
- public int start(int group):返回在以前的匹配操作期间,由给定组所捕获的子序列的初始索引
- public int end():返回最后匹配字符之后的偏移量
- public int end(int group):返回在以前的匹配操作期间,由给定组所捕获子序列的最后字符之后的偏移量
public static void main(String[] args) {
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());
}
3.3 捕获组
捕获匹配的子字符串。编号为零的第一个捕获是由整个正则表达式模式匹配的文本,其它捕获结果则根据左括号的顺序从1开始自动编号。
非命名捕获 (pattern):
public static void main(String[] args) {
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));
System.out.println("====================");
}
}
命名捕获 (?pattern):
将匹配的子字符串捕获到一个组名称或编号名称中。用于name的字符串不能包含任何符号,并且不能以数字开头。可以使用单引号代替尖括号。例如:(?‘name’)。
public static void main(String[] args) {
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"));
System.out.println("====================");
}
}
非捕获匹配 (?:pattern):
匹配pattern但不捕获该匹配的子表达式,即它是一个非捕获匹配,不存储以后使用的匹配。这对于用"or"字符(|)组合模式部件的情况很有用。例如,'industr(?:y|ies)'是比’industry|industries’更经济的表达式。
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(matcher.group(0));
// System.out.println(matcher.group(1));//错误,并不会捕获分组
System.out.println("====================");
}
}
非捕获匹配 (?=pattern):
它是一个非捕获匹配。例如,'Windows(?=95|98|NT|2000)'匹配"Windows 2000"中的"Windows",但是不匹配"Windows 3.1"中的"Windows"。
public static void main(String[] args) {
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));//错误,并不会捕获分组
System.out.println("====================");
}
String res = matcher.replaceAll("@@@@@");
System.out.println(res);
}
非捕获匹配 (?!pattern):
该表达式匹配不处于匹配pattern的字符串的起始点的搜索字符串。它是一个非捕获匹配。例如,'Windows (?!95|98|NT|2000)'匹配"Windows 3.1"中的"Windows",但是不匹配"Windows 2000"中的"Windows"。
public static void main(String[] args) {
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));//错误,并不会捕获分组
System.out.println("====================");
}
String res = matcher.replaceAll("@@@@@");
System.out.println(res);
}
4 测试案例(重点案例)
正则工具:正则表达式在线工具
4.1 匹配单个字符
4.1.1 匹配纯文本
有多个匹配结果,使用全局匹配:
字母大小写,使用i标志忽略大小写:
public static void main(String[] args) {
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());
}
}
4.1.2 匹配任意字符
使用.匹配任意字符:
4.1.3 匹配特殊字符
使用\转义字符:
4.2 匹配一组字符
4.2.1 匹配多个字符中的某一个
4.2.2 利用字符集合区间
字符区间并不仅限于数字,以下这些都是合法的字符区间:
- A-Z,匹配从A到Z的所有大写字母。
- a-z,匹配从a到z的所有小写字母。
- A-F,匹配从A到F的所有大写字母。
- A-z,匹配从ASCIⅡ字符A到ASCI字符z的所有字母。这个模式一般不常用,因为它还包含着[和等在ASCI字符表里排列在Z和a之间的字符。
4.2.3 取非匹配
集合中使用^
4.3 使用元字符
4.3.1 对特殊字符进行转义
4.3.2 匹配空白字符
元字符 | 说明 |
---|---|
[\b] | 回退(并删除)一个字符(Backsace键) |
\f | 换页符 |
\n | 换行符 |
\r | 回车符 |
\t | 制表符(Tab键) |
\v | 垂直制表符 |
4.3.3 匹配特定的字符类别
元字符 | 说明 |
---|---|
\d | 任何一个数字字符(等价于[0-9]) |
\D | 任何一个非数字字符(等价于[^0-9]) |
元字符 | 说明 |
---|---|
\s | 任何一个空白字符(等价于[\f\n\r\t\v]) |
\S | 任何一个非空白字符(等价于[^\f\n\r\t\v]) |
4.4 重复匹配
4.4.1 有多少个匹配
使用+,匹配一个或多个字符:
使用,匹配零个或多个字符:*
使用?,匹配零个或一个字符:
4.4.2 匹配的重复次数
使用{n},位重复匹配次数设定一个精确的值:
使用{m, n},位重复匹配次数设定一个区间:
使用{m,},匹配"至少重复多少次":
4.4.3 防止过度匹配
贪婪型元字符 | 懒惰型元字符 |
---|---|
* | *? |
+ | +? |
{n,} | {n,}? |
贪婪匹配的结果,将第一个B标签的开头和第二个B标签的结尾匹配为一对: | |
懒惰匹配的结果: | |
4.5 位置匹配
4.5.1 单词边界
使用\b来匹配一个单词的开始或结尾:
使用\B表明不匹配一个单词边界:
4.5.2 字符串边界
使用^匹配字符串的开头,$匹配字符串的结尾:
4.5.3 分行匹配模式
使用(?m)启用分行匹配模式:
public static void main(String[] args) {
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());
}
}
4.6 使用子表达式
4.6.1 子表达式
子表达式是一个更大的表达式的一部分;把一个表达式划分为一系列子表达式的目的是为了把那些子表达式当作一个独立元素来使用。子表达式必须用(和)括起来。
4.6.2 子表达式的嵌套
public static void main(String[] args) {
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());
}
}