Java 正则表达式
Java
的匹配默认为贪婪匹配,尽可能多的去匹配。
匹配规则
规则 | 作用 | 示例 | 示例说明 |
---|---|---|---|
[] | 可接收的字符列表 | [efg] | 可接收e、f、g中任意一个字符 |
[^] | 不接收的字符列表 | [^efg] | 除e、f、g之外的任意一个字符 |
- | 连字符 | A-Z | 任意一个大写字母 |
. | 匹配除\n为的任意字符 | a…b | 以a开头,b结尾,中间包括2个任意字符的长度为4的字符串 |
\\d | 匹配单个数字字符 | \\d{3}\\d? | 匹配 3 个或 4 个数字的字符串 |
\\D | 匹配单个非数字字符 | \\D(\\d)* | 以单个非数字开头, 后接任意个数的数字字符串 |
\\w | 匹配单个数字、大小写字母的字符以及下划线 | ||
\\W | 与 \\w 相反 | \\W+ | 以至少1个非数字字母开头的字符串 |
\\s | 匹配单个空白符 | ||
\\S | 匹配非空白符 | ||
(?i) | 符号之后的内容都不区分大小写了,除非用括号指定范围 | ||
^ | 指定起始字符 | 1+[a-z]* | 表示以至少一个数字开头, 后接任意个数的小写字母组成的字符串 |
$ | 指定结束字符 | [a-z]+$ | 表示以至少一个小写字母结尾的字符串 |
\\b | 匹配目标字符串的边界。这里的边界指的是字符串之后跟空白字符为边界,或者就是整个字符串的最后 | ||
\\B | 匹配目标字符串的非边界 | ||
? | 单独使用,匹配0或1个出现的前一个表达式;与其他匹配符号一起使用,表示非贪婪匹配 | ||
* | 匹配0个或多个出现的前一个表达式。 | ba*$ | 以 b 或 ba…结尾的字符串 |
+ | 匹配1个或多个出现的前一个表达式 | ^a+ | 开头至少有 1 个 a 的字符串 |
{n} | 匹配正好n次出现的前一个表达式 | ||
{n,} | 匹配至少n次出现的前一个表达式 | ||
{n,m} | 匹配至少n次且不超过m次出现的前一个表达式 | ||
| | 匹配两个或多个表达式之一 |
初体验
使用 Java
正则表达式匹配4个连在一起的阿拉伯数字。
import java.util.regex.Matcher;
import java.util.regex.Pattern;
/**
* 使用正则表达式匹配4个连在一起的阿拉伯数字。
*/
public class RegExp01 {
public static void main(String[] args) {
String content = "1889xcj+dsb&c1198cdj==fhs7863xbc-ndb34ms_ndc3dh%fjc";
// 目标:匹配4个连在一起的数字
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));
}
}
}
分析 matcher.find()
以上面给出的代码分析:
-
根据指定的规则,定位满足规则的 子字符串,比如: 1889。
-
找到后,将 子字符串 开始的索引记录到
matcher
对象的groups
属性中(int[] groups)。groups[0] = 0, 把该子字符串的 结束索引+1 的值记录到 group[1] = 4,
同时记录
oldLast
的值为子字符串
的结束索引+1
, 即 4,下次执行find()
时, 就从 4 开始匹配。 -
matcher.group(0)
根据
groups[0]
和group[1]
记录的位置, 从content
开始截取子字符串返回。源码
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(); }
-
如果再次指向
find
方法, 重复上述步骤(1198)groups[0]
变为 13,groups[1]
变为 17,
oldLast
变为 17, 下次进行find()
时, 就从17 开始,
然后根据groups[0]
和group[1]
记录的新位置, 从content
开始截取子字符串返回。
分组
以 (\d\d)(\d\d)
为匹配规则例:
-
整体(括号去掉)为第 0组,等效为
\d\d\d\d
-
第一个括号为第 1 组, 第二个括号为第 2 组···
-
如果将其替换为
((\d\d)(\d\d))
,那么第 0 组 和第 1组的内容是一致的。
分组实例
/**
* 分组
*/
public class RegExp02 {
public static void main(String[] args) {
String content = "1889xcj+dsb&c1198cdj==fhs7863xbc-ndb34ms_ndc3dh%fjc";
// 目标:匹配4个连在一起的数字
String regStr = "(\\d\\d)(\\d\\d)";
// 创建模式匹配对象
Pattern pattern = Pattern.compile(regStr);
// 创建匹配器
Matcher matcher = pattern.matcher(content);
while (matcher.find()) {
System.out.println("===========================");
System.out.println("0 组:" + matcher.group(0));
System.out.println("1 组:" + matcher.group(1));
System.out.println("2 组:" + matcher.group(2));
System.out.println("===========================");
}
}
}
运行结果:
分析 matcher.find()
-
根据指定的规则, 定位满足规则的子字符串(比如:1889);
-
找到后, 将 子字符串 开始的索引记录到
matcher
对象的groups
属性中;
记录0组:groups[0] = 0, 把该子字符串的 结束索引+1 的值记录到 group[1] = 4
记录1组:groups[2] = 0, groups[3] = 2
记录2组:groups[4] = 2, groups[4] = 4
同时记录 oldLast
的值为 子字符串的结束的 索引+1 的值,即 4,下次执行 find()
时, 就从 4 开始匹配
命名分组
语法格式:(?<name>patter)
命名分组又称命名捕获,将匹配的子字符串捕获到一个组名或编号名称中。
- 用于
name
的字符串不能包含任何的标点符号, 不能以数字开头; - 可以使用单引号替代尖括号, 例如:(?'name’patter);
- 可以使用
name
来获取对应的匹配结果。
以 \\d\\d(?<one>(?<two>\\d\\d)\\d\\d)
为例
public class RegExp03 {
public static void main(String[] args) {
String content = "zhangsanandlisi s128 nn112233zhang";
regStr = "\\d\\d(?<one>(?<two>\\d\\d)\\d\\d)";
pattern = Pattern.compile(regStr);
matcher = pattern.matcher(content);
while (matcher.find()) {
// 112233
System.out.println("0组" + matcher.group());
// 2233
System.out.println("one组:" + matcher.group("one"));
// 22
System.out.println("two组:" + matcher.group("two"));
}
}
}
特别(非捕获)的分组
(?:patter)
匹配 patter
,但不捕获该匹配的子表达式, 即它是一个非匹配分组。
不存储供以后使用的匹配。这对于用 or 字符(|)组合模式部件的情况很有用。
例如:industr(?:y|ies)
是比 industry|industries
更好的写法。
实例
public class RegExp04 {
public static void main(String[] args) {
String content = "张三是员工、张三是人类、张三是学生、张三是法外狂徒";
String regStr = "张三是(?:员工|人类|学生)";
Pattern pattern = Pattern.compile(regStr);
Matcher matcher = pattern.matcher(content);
while (matcher.find()) {
System.out.println("找到:" + matcher.group());
// 注意:不能使用以下代码
// matcher.group(1);
}
}
}
执行结果:
(?=patter)
它是一个非捕获匹配。
例如:‘Windows(?=95|98|NT|2000)’ 匹配 ‘Windows 2000’ 中的 ‘Windows’, 但是不匹配 ‘Windows 3.1’ 中的 ‘Windows’。
实例
public class RegExp05 {
public static void main(String[] args) {
String content = "张三是员工、张三是人类、张三是学生、张三是法外狂徒";
String regStr = "张三是(?=员工|人类|学生)";
Pattern pattern = Pattern.compile(regStr);
Matcher matcher = pattern.matcher(content);
while (matcher.find()) {
System.out.println("找到:" + matcher.group());
}
}
}
执行结果
(?!patter)
它是一个非捕获匹配,该表达式不存储匹配 patter
的字符串的起始点的搜索字符串。
例如:‘Windows(?!95|98|NT|2000)’ 匹配 ‘Windows 3.1’ 中的 ‘Windows’,
但是不匹配 ‘Windows 2000’ 中的 Windows
实例
public class RegExp06 {
public static void main(String[] args) {
String content = "张三是员工、张三是人类、张三是学生、张三是法外狂徒";
String regStr = "张三是(?!员工|人类|学生)";
Pattern pattern = Pattern.compile(regStr);
Matcher matcher = pattern.matcher(content);
while (matcher.find()) {
System.out.println("找到:" + matcher.group());
}
}
}
执行结果:
正则表达式转义符
Java
正则表达式的转移符号为 \\
。
在匹配如下符号是需要用到转移符:.
、*
、+
、$
、/
、\
、?
、[
、]
、{
、}
、<
、>
Pattern 和 Matcher
Pattern
Pattern
是一个正则表达式对象, 不能 new。
通过调用公共静态方法 compile(pattern)
,返回 Pattern
对象。该方法接收的一个正则表达式规则字符串作为它的第一个参数。
matches()
是这个类中常用的一个方法,用于整体匹配,可以用于验证输入的字符串是否符合要求。这个方法的底层实际上是调用了 Matcher
类中的 matches()
方法。
实例
public class RegExp07 {
public static void main(String[] args) {
String content = "hello abc hello";
String regStr = "hello"; // 整体匹配=false
boolean matches = Pattern.matches(regStr, content);
System.out.println("整体匹配=" + matches);
regStr = "hello.*"; // 整体匹配=true
matches = Pattern.matches(regStr, content);
}
}
Matcher
Marcher
是对输入字符串进行解释和匹配的引擎。
与 Pattern
类一样,Matcher
不能 new。
创建方式是调用 Pattern
对象的 matcher
方法来返回一个 Matcher
对象。
常用的方法有:
start()
:返回匹配的 初始索引- ``end()`:返回匹配的 结束结束索引+1
replaceAll()
:字符串的替换, 不改变原字符串
实例
public class RegExp08 {
public static void main(String[] args) {
String content = "hello edu jack tom hello smith hello";
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(content.substring(matcher.start(), matcher.end()));
}
// 替换。不会改变原来的字符串。
content = "zhangsan将拼音换为对应汉字";
regStr = "zhangsan";
pattern = Pattern.compile(regStr);
matcher = pattern.matcher(content);
String newContent = matcher.replaceAll("张三");
System.out.println(newContent); // 张三将拼音换为对应汉字
}
}
运行结果
反向引用
圆括号的内容被捕获以后, 可以在这个括号后被使用, 从而写出一个比较实用的匹配模式。这个我们称为反向引用, 这种引用既可以是在正则表达式内部, 也可以是正则表达式外部。
-
内部反向引用:
\\
分组号(\\d)\\1
:匹配的数字格式 xx,比如11
,22
(\\d)(\\d)\\1\\2
:匹配的数字格式 xyxy,比如1212
,0101
-
外部反向引用:
$
分组号
实例
找出格式为 xyyx
的数字字符串,比如 "1221"
public class RegExp09 {
public static void main(String[] args) {
String content = "1221 1222 1111 3478 5775 8118 8273";
String regStr = "(\\d)(\\d)\\2\\1";
Pattern pattern = Pattern.compile(regStr);
Matcher matcher = pattern.matcher(content);
while (matcher.find()) {
System.out.println("找到:" + matcher.group());
}
}
}
结巴去重
public class RegExp10 {
public static void main(String[] args) {
// 修改成: 我要学编程Java!
String content = "我...我要...学学学学...编程Java!";
// 去除所有的 . 号
String regStr = "[.]";
Pattern pattern = Pattern.compile(regStr);
Matcher matcher = pattern.matcher(content);
content = matcher.replaceAll("");
System.out.println(content); // 我我要学学学学编程Java!
// 去重:我我要学学学学编程Java!
regStr = "(.)\\1+";
pattern = Pattern.compile(regStr);
matcher = pattern.matcher(content);
while (matcher.find()) {
// 以下方法中 $1 是外部反向引用
content = Pattern.compile("(.)\\1+").matcher(content).replaceAll("$1");
}
System.out.println(content);
}
}
0-9 ↩︎