一、正则表达式引入
1、概述
-
正则表达式(regular expression,简称 RegExp)是处理文本的利器,是对字符串执行模式匹配的技术
-
例如,使用传统方式提取内容中的所有英文单词就要使用遍历,代码量大,且效率不高,但是正则表达式就可以简化这些操作
2、初体验
- 提取内容中所有的英文单词
String content = "由于在开发Oak语言时,尚且不存在运行字节码的硬件平台,所以为了在开发时可以对这种语言进行实验研究," +
"他们就在已有的硬件和软件平台基础上,按照自己所指定的规范,用软件建设了一个运行平台,整个系统除了比C++更加简单之外," +
"没有什么大的区别。1992年的夏天,当Oak语言开发成功后,研究者们向硬件生产商进行演示了Green操作系统、Oak的程序设计语言、" +
"类库和其硬件,以说服他们使用Oak语言生产硬件芯片,但是,硬件生产商并未对此产生极大的热情。因为他们认为,在所有人对Oak语言" +
"还一无所知的情况下,就生产硬件产品的风险实在太大了,所以Oak语言也就因为缺乏硬件的支持而无法进入市场,从而被搁置了下来。";
// 1、创建一个 Pattern 对象(模式对象,可以理解成一个正则表达式对象)
Pattern pattern = Pattern.compile("[a-zA-Z]+");
// 2、创建一个匹配器对象
// 理解:matcher 按照 pattern 到 content 中去匹配
// 找到就返回 true,未找到就返回 false
Matcher matcher = pattern.matcher(content);
// 3、开始循环匹配
while (matcher.find()) {
// 匹配到的内容放到 atcher.group(0) 中
System.out.println(matcher.group(0));
}
- 输出结果
Oak
C
Oak
Green
Oak
Oak
Oak
Oak
3、更多演示
- 提取内容中所有的数字
String content = "由于在开发Oak语言时,尚且不存在运行字节码的硬件平台,所以为了在开发时可以对这种语言进行实验研究," +
"他们就在已有的硬件和软件平台基础上,按照自己所指定的规范,用软件建设了一个运行平台,整个系统除了比C++更加简单之外," +
"没有什么大的区别。1992年的夏天,当Oak语言开发成功后,研究者们向硬件生产商进行演示了Green操作系统、Oak的程序设计语言、" +
"类库和其硬件,以说服他们使用Oak语言生产硬件芯片,但是,硬件生产商并未对此产生极大的热情。因为他们认为,在所有人对Oak语言" +
"还一无所知的情况下,就生产硬件产品的风险实在太大了,所以Oak语言也就因为缺乏硬件的支持而无法进入市场,从而被搁置了下来。";
Pattern pattern = Pattern.compile("[0-9]+");
Matcher matcher = pattern.matcher(content);
while (matcher.find()) {
System.out.println(matcher.group(0));
}
- 输出结果
1992
- 提取内容中所有的英文单词和数字
String content = "由于在开发Oak语言时,尚且不存在运行字节码的硬件平台,所以为了在开发时可以对这种语言进行实验研究," +
"他们就在已有的硬件和软件平台基础上,按照自己所指定的规范,用软件建设了一个运行平台,整个系统除了比C++更加简单之外," +
"没有什么大的区别。1992年的夏天,当Oak语言开发成功后,研究者们向硬件生产商进行演示了Green操作系统、Oak的程序设计语言、" +
"类库和其硬件,以说服他们使用Oak语言生产硬件芯片,但是,硬件生产商并未对此产生极大的热情。因为他们认为,在所有人对Oak语言" +
"还一无所知的情况下,就生产硬件产品的风险实在太大了,所以Oak语言也就因为缺乏硬件的支持而无法进入市场,从而被搁置了下来。";
Pattern pattern = Pattern.compile("([a-zA-Z]+)|([0-9]+)");
Matcher matcher = pattern.matcher(content);
while (matcher.find()) {
System.out.println(matcher.group(0));
}
- 输出结果
Oak
C
1992
Oak
Green
Oak
Oak
Oak
Oak
- 提取内容中的 IP 地址
String content = "10.1.1.1\n" +
"\n" +
"255.255.0.0\n" +
"\n" +
"10.1.1.1属于哪个网段?所在网段有多少个IP地址?该网段的广播地址是什么?\n" +
"\n" +
"答:\n" +
"\n" +
"10.1.1.1属于10.1.0.0网段。\n" +
"\n" +
"10.1.0.0网段可用的IP地址范围:10.1.0.1-10.1.255.254(65534)\n" +
"\n" +
"10.1.1.0网段的广播地址:10.1.255.255";
Pattern pattern = Pattern.compile("\\d+\\.\\d+\\.\\d+\\.\\d+");
Matcher matcher = pattern.matcher(content);
while (matcher.find()) {
System.out.println(matcher.group(0));
}
- 输出结果
10.1.1.1
255.255.0.0
10.1.1.1
10.1.1.1
10.1.0.0
10.1.0.0
10.1.0.1
10.1.255.254
10.1.1.0
10.1.255.255
二、正则表达式底层实现
1、基本介绍
- 一个正则表达式就是用某种模式去匹配字符串的一个公式,正则表达式不是只有 Java 才有,很多编程语言都支持正则表达式进行字符串操作
2、案例分析
(0)案例代码
- 匹配内容中的连续的四个数字
String content = "1998年12月8日,第二代Java平台的企业版J2EE发布。1999年6月,Sun公司发布了第二代Java平台(简称为Java2" +
")的3个版本:J2ME(Java2 Micro Edition,Java2平台的微型版),应用于移动、无线及有限资源的环境;J2SE" +
"(Java 2 Standard Edition,Java 2平台的标准版),3443应用于桌面环境;J2EE(Java 2Enterprise Edition," +
"Java 2平台的企业版),应用于基于Java的应用服务器。Java 2平台的发布," +
"是Java发展过程中最重要的一个里程碑,9889标志着Java的应用开始普及。";
// \\d 表示一个任意数字(0 ~ 9)
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));
}
- 输出结果
1998
1999
3443
9889
(1)matcher.find()
-
根据指定的规则,定位满足规则的字符串
-
定位到后,将子字待串的索引记录到 matcher 的属性
int[] groups;
中,例如,1998...
,会将子字符串开始的索引记录groups[0] = 0;
,会将子字符串结束的索引 + 1 记录groups[1] = 4;
-
同时记录 oldlast,即子字符串结束的索引 + 1,例如,这里就是 4,记录的作用是下次从这里开始匹配
(2)matcher.group(0)
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();
}
- 根据 groups[0] 和 groups[1] 的记录,从原字符串开始截取子字符串,左闭右开,这里就是 [0, 4)
3、分组分析
(0)案例代码
...
String regStr = "(\\d\\d)(\\d\\d)";
...
while (matcher.find()) {
System.out.println(matcher.group(0));
System.out.println("第一个分组:" + matcher.group(1));
System.out.println("第二个分组:" + matcher.group(2));
}
- 输出结果
1998
第一个分组:19
第二个分组:98
1999
第一个分组:19
第二个分组:99
3443
第一个分组:34
第二个分组:43
9889
第一个分组:98
第二个分组:89
(1)分组概述
- 正则表达式里有小括号(())表示分组,第一个小括号表示第一组,第二个小括号表示第二组
(2)matcher.find()
-
根据指定的规则,定位满足规则的字符串
-
定位到后,将子字待串的索引记录到 matcher 的属性
int[] groups;
中,例如,1998...
,会将子字符串开始的索引记录groups[0] = 0;
,会将子字符串结束的索引 + 1 记录groups[1] = 4;
,对第一个分组进行索引记录groups[2] = 0;
groups[3] = 2;
,对第二个分组进行索引记录groups[4] = 2;
groups[5] = 4;
-
同时记录 oldlast,即子字符串结束的索引 + 1,例如,这里就是 4,记录的作用是下次从这里开始匹配
(3)matcher.group()
-
matcher.group(0) 表示匹配到的子字符串
-
matcher.group(1) 表示匹配到的子字符串的第一个分组
-
matcher.group(2) 表示匹配到的子字符串的第二个分组
- 注:分组数量不能越界,否则报错
三、元字符
0、转义符
(1)基本介绍
-
在使用正则表达式去检索某些特殊字符的时候,需要用到转义符(双斜杠,
\\
),否则检索不到结果,甚至会报错的 -
注:在其他语言的正则表达式中,转义符是单斜杠(
\
) -
如下是需要使用转义符的符号
*
+
()
[]
{}
$
/\
?
^
(2)演示
- 错误演示
String content = "123(456(";
String regStr = "(";
Pattern pattern = Pattern.compile(regStr);
Matcher matcher = pattern.matcher(content);
while (matcher.find()) {
System.out.println(matcher.group(0));
}
- 输出结果
Exception in thread "main" java.util.regex.PatternSyntaxException: Unclosed group near index 1
(
- 使用转义符正确匹配
String content = "123(456(";
String regStr = "\\(";
Pattern pattern = Pattern.compile(regStr);
Matcher matcher = pattern.matcher(content);
while (matcher.find()) {
System.out.println(matcher.group(0));
}
- 输出结果
(
(
1、字符匹配符
(1)基本介绍
符号 | 说明 | 示例 |
---|---|---|
[] |
可接收的字符序列 |
[abcd] :a、b、c、d 中的任意一个字符 |
[^] |
可接收的字符序列 |
[^bc] :除 a、b、c 的任意一个字符,包括数字和特殊符号 |
- |
连字符 |
A-Z :任意单个大写字面 |
. |
匹配除 \n 以外任意字符 |
a..b :以 a 开头,b 结尾,中间包括 2 个任意字符的长度为 4 的字符串 |
\\d |
匹配单个数字字符 相当于 [0-9]
|
\\d{3}(\\d)? :匹配 3 个或 4 个数字的字符串 |
\\D |
匹配单个非数字字符 |
\\D(\\d)* :以单个非数字字符开头,后接任意个数字字符串 |
\\w |
匹配单个数字和大小写字母字符 相当于 [0-9a-zA-Z]
|
\\d{3}\\w{4} :以 3 个数字字符开头的长度为 7 的数字字母字符串 |
\\W |
匹配单个非数字和大小写字母字符 相当于 [^0-9a-zA-Z]
|
\\W+\\d{2} :以至少 1 个非数字字母字符开头,2 个数字字符结尾的字符串 |
(2)演示
- 匹配 a - z 之间的任意一个字符(
[0-9]
、[A-Z]
同理)
String content = "a11c8";
String regStr = "[a-z]";
Pattern pattern = Pattern.compile(regStr);
Matcher matcher = pattern.matcher(content);
while (matcher.find()) {
System.out.println(matcher.group(0));
}
- 输出结果
a
c
- 不区分大小写匹配
String content = "a11c8ABC123abc456Abc";
String regStr = "(?i)abc";
Pattern pattern = Pattern.compile(regStr);
Matcher matcher = pattern.matcher(content);
while (matcher.find()) {
System.out.println(matcher.group(0));
}
// 等同于
String content = "a11c8ABC123abc456Abc";
String regStr = "abc";
Pattern pattern = Pattern.compile(regStr, Pattern.CASE_INSENSITIVE);
Matcher matcher = pattern.matcher(content);
while (matcher.find()) {
System.out.println(matcher.group(0));
}
示例 | 说明 |
---|---|
(?i)abc |
a、b、c 都不区分大小写 |
a(?i)bc |
b、c 不区分大小写 |
a((?i)b)c |
只有 b 不区分大小写 |
- 输出结果
ABC
abc
Abc
- 匹配不是 a - z 之间的任意一个字符(
[0-9]
、[A-Z]
同理)
String content = "a11c8";
String regStr = "[^a-z]";
Pattern pattern = Pattern.compile(regStr);
Matcher matcher = pattern.matcher(content);
while (matcher.find()) {
System.out.println(matcher.group(0));
}
- 输出结果
1
1
8
- 匹配 a、b、c、d 中的任意一个字符(
[^abcd]
则是匹配不是 a、b、c、d 中的任意一个字符)
String content = "a11c8zyx";
String regStr = "[abcd]";
Pattern pattern = Pattern.compile(regStr);
Matcher matcher = pattern.matcher(content);
while (matcher.find()) {
System.out.println(matcher.group(0));
}
- 输出结果
a
c
- 匹配字母、数字、下划线
String content = "a11c8zyx&*#@_";
String regStr = "\\w";
Pattern pattern = Pattern.compile(regStr);
Matcher matcher = pattern.matcher(content);
while (matcher.find()) {
System.out.println(matcher.group(0));
}
- 输出结果
a
1
1
c
8
z
y
x
_
- 匹配非字母、数字、下划线
String content = "a11c8zyx&*#@_";
String regStr = "\\W";
Pattern pattern = Pattern.compile(regStr);
Matcher matcher = pattern.matcher(content);
while (matcher.find()) {
System.out.println(matcher.group(0));
}
- 输出结果
&
*
#
@
- 匹配任何空白字符(空格、制表符等)
String content = "a1 1c8zy x&*#@_";
String regStr = "\\s";
Pattern pattern = Pattern.compile(regStr);
Matcher matcher = pattern.matcher(content);
while (matcher.find()) {
System.out.println("-" + matcher.group(0) + "-");
}
- 输出结果
- -
- -
- -
- 匹配任何非空白字符
String content = "a1 1c8zy x&*#@_";
String regStr = "\\S";
Pattern pattern = Pattern.compile(regStr);
Matcher matcher = pattern.matcher(content);
while (matcher.find()) {
System.out.println(matcher.group(0));
}
- 输出结果
a
1
1
c
8
z
y
x
&
*
#
@
_
- 匹配除
\n
之外的所有字符
String content = "a1 1cy\n#@_";
String regStr = ".";
Pattern pattern = Pattern.compile(regStr);
Matcher matcher = pattern.matcher(content);
while (matcher.find()) {
System.out.println(matcher.group(0));
}
- 输出结果
a
1
1
c
y
#
@
_
- 匹配
.
自身
String content = "a.1 1c.y\n#@_";
String regStr = "\\.";
Pattern pattern = Pattern.compile(regStr);
Matcher matcher = pattern.matcher(content);
while (matcher.find()) {
System.out.println(matcher.group(0));
}
- 输出结果
.
.
2、选择匹配符
(1)基本介绍
符号 | 说明 | 示例 |
---|---|---|
` | ` | 匹配 ` |
(2)演示
String content = "sunke 孙 helloworld";
String regStr = "sunke|孙|hello";
Pattern pattern = Pattern.compile(regStr);
Matcher matcher = pattern.matcher(content);
while (matcher.find()) {
System.out.println(matcher.group(0));
}
- 输出结果
sunke
孙
hello
3、限定符
(1)基本介绍
- 限定符用于指定其前面的字符和组合项连续出现多少次
符号 | 说明 | 示例 |
---|---|---|
* |
指定字符重复 0 次或 n 次 |
(abc)* :包含任意个 abc 的字符串,等效于 \w*
|
+ |
指定字符重复 1 次或 n 次 |
m+(abc)* :以至少 1 个 m 开头,后接任意个 abc 的字符串 |
? |
指定字符重复 0 次或 1 次 |
m+abc? :以至少 1 个 m 开头,后接 ab 或 abc 的字符串 |
{n} |
只能输入 n 个字符 |
[abcd]{3} :由 a、b、c、d 组成的任意长度为 3 的字符串 |
{n,} |
指定至少 n 个匹配 |
[abcd]{3,} :由 a、b、c、d 组成的任意长度不小于 3 的字符串 |
{n,m} |
指定至少 n 个但不多于 m 个匹配 |
[abcd]{3,5} :由 a、b、c、d 组成的任意长度不小于 3 且不大于 5 的字符串 |
(2)演示
- 匹配 1111
String content = "11123111123456123";
String regStr = "1{4}";
Pattern pattern = Pattern.compile(regStr);
Matcher matcher = pattern.matcher(content);
while (matcher.find()) {
System.out.println(matcher.group(0));
}
- 输出结果
1111
- 匹配两位数字
String content = "123abc4567";
String regStr = "\\d{2}";
Pattern pattern = Pattern.compile(regStr);
Matcher matcher = pattern.matcher(content);
while (matcher.find()) {
System.out.println(matcher.group(0));
}
- 输出结果
12
45
67
- 匹配 aaa 或 aaaa
- 注:Java 中的正则匹配默认是贪婪匹配,即尽可能多的匹配
String content = "aaaa123123aaa";
String regStr = "a{3,4}";
Pattern pattern = Pattern.compile(regStr);
Matcher matcher = pattern.matcher(content);
while (matcher.find()) {
System.out