正则表达式
整理BY:Misayaas
内容来自廖雪峰的官方网站: https://www.liaoxuefeng.com/
正则表达式简介
正则表达式可以用字符串来描述规则,并用来匹配字符串
例如,判断手机号,我们用正则表达式\d{11}
:1
2
3boolean isValidMobileNumber(String s) {
return s.matches("\\d{11}");
}
要判断用户输入的年份是否是20##
年,我们先写出规则如下:
一共有4个字符,分别是:2
,0
,0~9任意数字
,0~9任意数字
。
对应的正则表达式就是:20\d\d
,其中\d
表示任意一个数字。
把正则表达式转换为Java字符串就变成了20\\d\\d
,注意Java字符串用\\
表示\
匹配规则
正则表达式的匹配规则是从左到右按规则匹配
对于正则表达式
abc
来说,它只能精确地匹配字符串"abc"
,不能匹配"ab"
,"Abc"
,"abcd"
等其他任何字符串。如果正则表达式有特殊字符,那就需要用
\
转义。例如,正则表达式a\&c
,其中\&
是用来匹配特殊字符&
的,它能精确匹配字符串"a&c"
,但不能匹配"ac"
、"a-c"
、"a&&c"
等。两个
\\
实际上表示的是一个\
匹配任意字符
我们可以用.
匹配一个任意字符
.
匹配一个字符且仅限一个字符
匹配数字
如果我们只想匹配0
~9
这样的数字,可以用\d
匹配
\d
仅限单个数字字符
匹配常用字符
用\w
可以匹配一个字母、数字或下划线,w的意思是word
匹配空白字符
用\s
可以匹配一个空格字符,注意空格字符不但包括空格,还包括tab字符(在Java中用\t
表示)
匹配非数字
\D
则匹配一个非数字
类似的,\W
可以匹配\w
不能匹配的字符,\S
可以匹配\s
不能匹配的字符,这几个正好是反着来的
重复匹配
修饰符*可以匹配任意个字符,包括0个字符
我们用A\d*
可以匹配:
A
:因为\d*
可以匹配0个数字;A0
:因为\d*
可以匹配1个数字0
;A380
:因为\d*
可以匹配多个数字380
修饰符+
可以匹配至少一个字符
修饰符?
可以匹配0个或一个字符
修饰符{n}
可以精确指定n个字符
- 如果我们想指定匹配n~m个字符怎么办?用修饰符
{n,m}
就可以 - 如果没有上限,那么修饰符
{n,}
就可以匹配至少n个字符
单个字符匹配规则
正则表达式 | 规则 | 可以匹配 |
---|---|---|
A | 指定字符 | A |
\u845c | 指定Unicode字符 | 和 |
. | 任意字符 | a , b , & , 0 |
\d | 数字0~9 | 0~9 |
\w | 大小写字母,数字和下划线 | a~z , A~Z , 0~9 |
\s | 空格、Tab键 | 空格、Tab |
\D | 非数字 | a , A , & , _ , … |
\W | 非\w | & , @ , 中 , … |
\S | 非\s | a , A , & , _ , … |
多个字符的匹配规则
正则表达式 | 规则 | 可以匹配 |
---|---|---|
A* | 任意个数字符 | 空,A ,AA , … |
A+ | 至少一个字符 | A ,AA , … |
A? | 0个或1个字符 | 空 , A |
\d | 数字0~9 | 0~9 |
A{3} | 指定个数字符 | AAA |
A{2,} | 至少n个字符 | AA , AAA , … |
A{0,3} | 最多n个字符 | 空 , A ,AA , AAA |
复杂匹配规则
匹配开头和结尾
用正则表达式进行多行匹配时,我们用^
表示开头,,可以匹配"A001"
、"A380"
。
匹配指定范围
使用[...]
可以匹配范围内的字符,例如,[123456789]
可以匹配1
~9
,这样就可以写出上述电话号码的规则:[1-9]\d{6,7}
要匹配大小写不限的十六进制数,比如1A2b3c
,我们可以这样写:[0-9a-fA-F]
,它表示一共可以匹配以下任意范围的字符:
0-9
:字符0
~9
;a-f
:字符a
~f
;A-F
:字符A
~F
。
[...]
还有一种排除法,即不包含指定范围的字符。假设我们要匹配任意字符,但不包括数字,可以写[^1-9]{3}
或规则匹配
用|
连接的两个正则规则是或规则,例如,AB|CD
表示可以匹配AB
或CD
使用括号
现在我们想要匹配字符串learn java
、learn php
和learn go
,可以把公共部分提出来,然后用(...)
把子规则括起来表示成learn\\s(java|php|go)
|正则表达式|规则|可以匹配|
|—-|—-|—-|
|^|开头|字符串开头|
|$|结尾|字符串结尾|
|[ABC]|[…]内任意字符|A,B,C|
|[A-F0-9xy]|指定范围的字符|A,…,F,0,…9,x,y|
|A-F|指定范围外的任意字符|非A-F|
|AB|CD|EF
|AB或CD或EF|AB,CD,EF|
分组匹配
(...)
还有一个重要作用,就是分组匹配
提取匹配的子串正确的方法是用(...)
先把要提取的规则分组,把上述正则表达式变为(\d{3,4})\-(\d{6,8})
按括号提取子串必须引入java.util.regex
包,用Pattern
对象匹配,匹配后获得一个Matcher
对象,如果匹配成功,就可以直接从Matcher.group(index)
返回子串1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16import java.util.regex.*;
public class Main {
public static void main(String[] args) {
Pattern p = Pattern.compile("(\\d{3,4})\\-(\\d{7,8})");
Matcher m = p.matcher("010-12345678");
if (m.matches()) {
String g1 = m.group(1);
String g2 = m.group(2);
System.out.println(g1);
System.out.println(g2);
} else {
System.out.println("匹配失败!");
}
}
}
- 传入0会得到整个字串
Pattern
但是反复使用String.matches()
对同一个正则表达式进行多次匹配效率较低,因为每次都会创建出一样的Pattern
对象
完全可以先创建出一个Pattern
对象,然后反复使用,就可以实现编译一次,多次匹配1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19import java.util.regex.*;
public class Main {
public static void main(String[] args) {
Pattern pattern = Pattern.compile("(\\d{3,4})\\-(\\d{7,8})");
psattern.matcher("010-12345678").matches(); // true
pattern.matcher("021-123456").matche(); // false
pattern.matcher("022#1234567").matches(); // false
// 获得Matcher对象:
Matcher matcher = pattern.matcher("010-12345678");
if (matcher.matches()) {
String whole = matcher.group(0); // "010-12345678", 0表示匹配的整个字符串
String area = matcher.group(1); // "010", 1表示匹配的第1个子串
String tel = matcher.group(2); // "12345678", 2表示匹配的第2个子串
System.out.println(area);
System.out.println(tel);
}
}
}
非贪婪匹配
正则表达式默认使用贪婪匹配:任何一个规则,它总是尽可能多地向后匹配,因此,\d+
总是会把后面的0
包含进来
- (\d+)(0) 在规则
\d+
后面加个?
即可表示*非贪婪匹配1
2
3
4
5
6
7
8
9
10
11
12import java.util.regex.*;
public class Main {
public static void main(String[] args) {
Pattern pattern = Pattern.compile("(\\d+?)(0*)");
Matcher matcher = pattern.matcher("1230000");
if (matcher.matches()) {
System.out.println("group1=" + matcher.group(1)); // "123"
System.out.println("group2=" + matcher.group(2)); // "0000"
}
}
}
搜索与替换
分割字符串
String.split()
方法传入的正是正则表达式1
2
3"a b c".split("\\s"); // { "a", "b", "c" }
"a b c".split("\\s"); // { "a", "b", "", "c" }
"a, b ;; c".split("[\\,\\;\\s]+"); // { "a", "b", "c" }
搜索字符串
1 | import java.util.regex.*; |
我们获取到Matcher
对象后,不需要调用matches()
方法(因为匹配整个串肯定返回false),而是反复调用find()
方法,在整个串中搜索能匹配上\\wo\\w
规则的子串,并打印出来。这种方式比String.indexOf()
要灵活得多,因为我们搜索的规则是3个字符:中间必须是o
,前后两个必须是字符[A-Za-z0-9_]
替换字符串
使用正则表达式替换字符串可以直接调用String.replaceAll()
,它的第一个参数是正则表达式,第二个参数是待替换的字符串1
2
3
4
5
6
7public class Main {
public static void main(String[] args) {
String s = "The quick\t\t brown fox jumps over the lazy dog.";
String r = s.replaceAll("\\s+", " ");
System.out.println(r); // "The quick brown fox jumps over the lazy dog."
}
}
反向引用
如果我们要把搜索到的指定字符串按规则替换,比如前后各加一个<b>xxxx</b>
,这个时候,使用replaceAll()
的时候,我们传入的第二个参数可以使用$1
、$2
来反向引用匹配到的子串1
2
3
4
5
6
7public class Main {
public static void main(String[] args) {
String s = "the quick brown fox jumps over the lazy dog.";
String r = s.replaceAll("\\s([a-z]{4})\\s", " <b>$1</b> ");
System.out.println(r);
}
}
上述代码的运行结果是:
1 | the quick brown fox jumps <b>over</b> the <b>lazy</b> dog. |
它实际上把任何4字符单词的前后用<b>xxxx</b>
括起来。实现替换的关键就在于" <b>$1</b> "
,它用匹配的分组子串([a-z]{4})
替换了$1