正则表达式
正则表达式是一套标准,他可以用于任何语言. Java 标准库的 包内置了正则表达式引擎,在 Java 程序中使用正则表达式非常简单.
例如: 判断用户输入的年份是否为 20xx
年
对应的正则表达式则是: 20\d\d
, \d
表示任意一个数字
而在 Java 中, \\
才表示一个 \
,所以对应的正则是 20\\d\\d
public static void main(String[] args) {
String regex = "20\\d\\d";
System.out.println("2022".matches(regex));//true
System.out.println("2122".matches(regex));//false
}
零.匹配规则表
单个字符的匹配规则如下:
正则表达式 | 规则 | 可以匹配 |
---|---|---|
A |
指定字符 | A |
\u548c |
指定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 ,AAA ,…… |
A+ |
至少1个字符 |
A ,AA ,AAA ,…… |
A? |
0个或1个字符 | 空,A
|
A{3} |
指定个数字符 | AAA |
A{2,3} |
指定范围个数字符 |
AA ,AAA
|
A{2,} |
至少n个字符 |
AA ,AAA ,AAAA ,…… |
A{0,3} |
最多n个字符 | 空,A ,AA ,AAA
|
复杂匹配规则主要有:
正则表达式 | 规则 | 可以匹配 |
---|---|---|
^ | 开头 | 字符串开头 |
$ | 结尾 | 字符串结束 |
[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
|
一.匹配规则
正则表达式的匹配规则是从左到右按规则进行匹配.
对于字符串 abc
来说,就只能匹配 abc
.
如果正则表达式有特殊字符,而想与这个特殊字符进行匹配,就得使用 \
对这个字符进行转义. 例如, \.
匹配 .
, \&
匹配 &
如果想匹配非ASCII字符,例如中文,那就用\u####
的十六进制表示,例如:a\u548cc
匹配字符串"a和c"
,中文字符和
的Unicode编码是548c
正则表达式表:(不完全)
ABC… | 信件 |
---|---|
123… | 数字 |
\d | 任何单个数字 |
\D | 任何单个非数字字符 |
. | 表示任何单个字符 |
\. | 匹配’‘.’’ |
[ABC] | 只有 a、b 或 c |
[^abc] | 不是a,b,也不是c |
[a-z] | 字符 a 到 z |
[0-9] | 数字 0 到 9 |
\w | 任何字母数字字符 |
\W | 任何非字母数字字符 |
{m} | m次重复 |
{m,n} | m 到 n 次重复 |
* | 零次或多次重复 |
+ | 一次或多次重复 |
? | 可选字符,匹配0或1个字符 |
\s | 任何空格 |
\S | 任何非空白字符 |
^…$ | ^开始和&结束 |
(……) | 捕获组 |
(a(bc)) | 捕获子组 |
(.*) | 全部捕获 |
(abc|def) | 匹配 abc 或 def |
\w | 匹配一个字母、数字或下划线 |
1.匹配任意字符
.
可以匹配一个任意字符
表达式
-
"abc"
,因为.
可以匹配字符b
; -
"a&c"
,因为.
可以匹配字符&
; -
"acc"
,因为.
可以匹配字符c
。
2.匹配数字和非数字
匹配0
~9
这样的数字,可以用\d
匹配。而 \D
则匹配一个非数字
表达式 00\d\D
-
"008A"
;\d
可以匹配到数字8
,\D
可以匹配非数字字符A
-
"009#"
;\d
可以匹配到数字9
,\D
可以匹配非数字字符#
-
"002*"
;\d
可以匹配到数字2
,\D
可以匹配非数字字符*
3.匹配空格字符
用\s
可以匹配一个空格字符,注意空格字符不但包括空格
,还包括tab字符(在Java中用\t
表示)。例如,a\sc
可以匹配:
-
"a c"
,因为\s
可以匹配空格字符 -
"a c"
,因为\s
可以匹配 tab 字符\t
。
它不能匹配"ac"
,"abc"
等。
4.匹配常用字符
用\w
可以匹配一个字母、数字或下划线.w的意思是word。例如,java\w
可以匹配:
-
"javac"
,因为\w
可以匹配英文字符c
; -
"java9"
,因为\w
可以匹配数字字符9
;。 -
"java_"
,因为\w
可以匹配下划线_
。
它不能匹配"java#"
,"java "
,因为\w
不能匹配#
、空格等字符。
类似的,\W
可以匹配\w
不能匹配的字符,\S
可以匹配\s
不能匹配的字符,这几个正好是反着来的。
5.重复匹配
像上面的正则,都是只能匹配单个,\d
只能匹配单个任意数字,\D
只能匹配单个非数字…
1)*
修饰符*
可以匹配任意个字符,包括0个字符,匹配 0 个或多个字符
们用A\d*
可以匹配:
-
A
:因为\d*
可以匹配0个数字; -
A0
:因为\d*
可以匹配1个数字0
; -
A380
:因为\d*
可以匹配多个数字380
。
2)+
**修饰符+
可以匹配至少一个字符。**匹配一个或多个字符
用A\d+
可以匹配:
-
A0
:因为\d+
可以匹配1个数字0
; -
A380
:因为\d+
可以匹配多个数字380
。
但 A
是无法被匹配到的,因为\d+
是要匹配至少一个数字
3)?
修饰符?
可以匹配0个或一个字符
用A\d?
可以匹配:
-
A
:因为\d?
可以匹配0个数字; -
A0
:因为\d?
可以匹配1个数字0
。
4){m}
匹配 m 次重复字符
A\d{3}
可以精确匹配:
-
A380
:因为\d{3}
可以匹配3个数字380
。
5){m,n}
匹配 m ~ n 次重复字符
A\d{3,5}
可以精确匹配:
-
A380
:因为\d{3,5}
可以匹配3个数字380
; -
A3800
:因为\d{3,5}
可以匹配4个数字3800
; -
A38000
:因为\d{3,5}
可以匹配5个数字38000
。
二.复杂的匹配规则
1.匹配开头和结尾
用正则表达式进行多行匹配时,我们用^
表示开头,$
表示结尾。例如,^A\d{3}$
,可以匹配"A001"
、"A380"
。
2.匹配指定范围
[...]
可以匹配范围内的单个字符,例如[123465Adfsafh%&^(]
则,只要是包含在里面的,都可以被匹配到.
public static void main(String[] args) {
String regex = "[123465Adfsafh%&^(]";
System.out.println("1".matches(regex));//true
System.out.println("d".matches(regex));//true
System.out.println("&".matches(regex));//true
System.out.println("(".matches(regex));//true
System.out.println("123".matches(regex));//false,因为只能范围匹配单个字符
}
[1-9]
:表示匹配数字1到数字9
要匹配大小写不限的十六进制数,比如1A2b3c
,我们可以这样写:[0-9a-fA-F]
,它表示一共可以匹配以下任意范围的字符:
-
0-9
:字符0
~9
; -
a-f
:字符a
~f
; -
A-F
:字符A
~F
。
[0-9a-fA-F]{6}
,则表示匹配6位十六进制数
3.或规则匹配
用|
连接的两个正则规则是或规则,例如,AB|CD
表示可以匹配AB
或CD
。
三.分组匹配
1.使用 () 对字符串进行分组
(...)
可以用来把一个子规则括起来.还有一个重要作用,就是用于分组匹配
前面说过,使用正则匹配区号-电话号码
,写出的正则是\d{3,4}\-\d{6,8}
但匹配完后,如果需要将区号和电话号码单独提取出来呢?
如果不知道分组匹配,则会马上联想到 Java 标准库中 ()
和 ()
方法.虽然可以实现,但会麻烦些,且很难通用.
分组匹配则是一个更通用,且更简单的方法.
(\d{3,4})\-(\d{6,8})
这样一来,就分组完成了.
但还要对子串进行提取,接下来介绍两个对象
和 Matcher
之前是使用 (String regex)
来判断字符串是否和正则的匹配,若是要提取子串,就不能用这样简单的判断方法了.
需要引入包,用
Pattern
对象匹配,匹配后获得一个Matcher
对象,如果匹配成功,就可以直接从(index)
返回子串
public static void main(String[] args) {
//用Pattern对象匹配,匹配后获得一个Matcher对象,
// 如果匹配成功,就可以直接从(index)返回子串:
String regex = "(\\d{3,4})\\-(\\d{6,8})";
Pattern pattern = Pattern.compile(regex);
//创建一个匹配器,该匹配器将给定的输入与此模式进行匹配。
Matcher matcher = pattern.matcher("010-12345678");
if(matcher.matches()){
String g1 = matcher.group(1);//010
String g2 = matcher.group(2);//12345678
System.out.println(g1);
System.out.println(g2);
}else{
System.out.println("匹配失败");
}
}
Pattern 类:
正则表达式的编译表示。
必须首先将正则表达式(指定为字符串)编译为此类的实例。 然后将所得的图案可以被用来创建一个Matcher对象可以匹配任意character sequences针对正则表达式。 执行匹配的所有状态都驻留在匹配器中,所以许多匹配者可以共享相同的模式。
因此,典型的调用序列
Pattern p = Pattern.compile("a*b");//先进行编译,得到 Pattern 对象
Matcher m = p.matcher("aaaaab");//进行匹配,返回一个 Matcher 对象
boolean b = m.matches();//相当于 (String regex),这里调用了 Matcher 对象的 matches()方法
这个类定义了一个matches方法,以便在正则表达式只使用一次时方便。该方法编译一个表达式,并在单个调用中匹配输入序列。 该声明
boolean b = Pattern.matches("a*b", "aaaaab");//这里每次调用都会进行编译一次,也就是会创建一个 Pattern 对象
相当于上面的三个语句,尽管对于重复匹配,它的效率较低,因为它不允许编译的模式被重用。
这个类的实例是不可变的,可以安全地被多个并发线程使用
Matcher类:
- 通过调用模式的matcher方法从模式创建匹配器。 创建后,可以使用匹配器执行三种不同类型的匹配操作:
- matches方法尝试将整个输入序列与模式进行匹配。
- lookingAt方法尝试将起始于输入序列的输入序列与模式进行匹配。
- find方法扫描输入序列,寻找匹配模式的下一个子序列。
这些方法中的每一个返回一个指示成功或失败的布尔值。通过查询匹配器的状态可以获得有关成功匹配的更多信息。
返回值 | 方法名 |
---|---|
boolean |
matches() 尝试将整个区域与模式进行匹配。 |
String |
group(int group) 返回在上一次匹配操作期间由给定组捕获的输入子序列 |
返回在上一次匹配操作期间由给定组捕获的输入子序列。
从一开始。 组零表示整个模式,因此表达式(0)
相当于()
。 如果匹配成功,但指定的组失败,则匹配输入序列的任何部分,则返回null
。
group(1)
匹配第一个子字符串(括号),group(2)
则是第二个,以此类推
返回值 | 方法名 |
---|---|
boolean |
find() 尝试找到匹配模式的输入序列的下一个子序列 |
boolean |
find(int start) 重新设置该匹配器,然后尝试从指定的索引开始找到匹配模式的输入序列的下一个子序列。 |
int |
start() 返回上一个匹配的起始索引。 |
int |
end() 返回最后一个字符匹配后的偏移量。 |
find()
: 该方法从该匹配器区域的开始处开始,或者如果该方法的先前调用成功,并且匹配器尚未被重置,则在与之前匹配不匹配的第一个字符处。
find(int start)
: 如果匹配成功可以通过start,end
和group
方法,以及随后的调用能够得到那么更多的信息find()方法将在不受此匹配匹配的第一个字符开始。
如果匹配成功可以通过start,end
和group
方法来获得,然后更多的信息。
简而言之就是用于寻找 Pattern
编译后的正则编译表示,找到的就给其中的 start()
和 end()
设置下标
在提取子串前需要先调用 matches()
判断是否匹配成功
例子2:
利用分组匹配,从字符串"23:01:59"提取时、分、秒。
public static void main(String[] args) {
// 利用分组匹配,从字符串"23:01:59"提取时、分、秒。
// String regex = "[0-2]\\d:[0-5]\\d:[0-5]\\d";
// ("23:01:59".matches(regex));
Pattern pattern = Pattern.compile("([0-2]\\d):([0-5]\\d):([0-5]\\d)");
Matcher matcher = pattern.matcher("23:01:59");
if(matcher.matches()){
String s1 = matcher.group(1);//23
String s2 = matcher.group(2);//01
String s3 = matcher.group(3);//59
String s = matcher.group();//23:01:59
String ss = matcher.group(0);//23:01:59
System.out.println(s1);
System.out.println(s2);
System.out.println(s3);
System.out.println(s);
System.out.println(ss);
}else{
System.out.println("匹配失败");
}
}
四.非贪婪匹配
正则表达式匹配默认使用贪婪匹配,可以使用?
表示对某一规则进行非贪婪匹配。
贪婪匹配会尽可能多的匹配,而非贪婪匹配则会尽可能少的匹配
例子:
给定一个字符串标识的数字,判断数字末尾 0 的个数.例如
-
"123000"
:3个0
-
"10100"
:2个0
-
"1001"
:0个0
正则表达式: (\d+)(0*)
public static void main(String[] args) {
String regex = "(\\d+)(0*)";
Pattern pattern = Pattern.compile(regex);
//创建一个匹配器,该匹配器将给定的输入与此模式进行匹配。
Matcher matcher = pattern.matcher("123000");
if(matcher.matches()){
System.out.println("group1: "+matcher.group(1));//group1: 123000
System.out.println("group2: "+matcher.group(2));//group2:
System.out.println("末尾0的个数: "+matcher.group(2).length());//末尾0的个数: 0
}
}
我们的预期是分成两组,一组是"123",一组是"000"
而当前结果是 “123000”,“”.
这是因为正则表达式默认使用贪婪匹配: 任何一个规则,它总是尽可能的向后匹配,因此, \d+
总是会把后面的0
包含进来.要让\d+
尽量少匹配,让0*
尽量多匹配,我们就必须让\d+
使用非贪婪匹配。在规则\d+
后面加个?
即可表示非贪婪匹配。
public static void main(String[] args) {
String regex = "(\\d+?)(0*)";
Pattern pattern = Pattern.compile(regex);
//创建一个匹配器,该匹配器将给定的输入与此模式进行匹配。
Matcher matcher = pattern.matcher("1203000");
if(matcher.matches()){
System.out.println("group1: "+matcher.group(1));//group1: 1203
System.out.println("group2: "+matcher.group(2));//group2: 000
System.out.println("末尾0的个数: "+matcher.group(2).length());//末尾0的个数: 3
}
}
在之前学过,?
表示匹配0个或1个,具体匹配啥看前面.比如 /d?
则表示匹配0个或1个数字
而在表示非贪婪匹配的时候,一般多是 +
,*
这样表示范围,模糊的匹配
因此,给定一个匹配规则,加上?
后就变成了非贪婪匹配。
分析(\d??)(9*)
:
\d?
表示匹配0个或1个数字,后面的?
表示非贪婪匹配, 因此,给定字符串"9999"
,匹配到的两个子串分别是""
和"9999"
,因为对于\d?
来说,可以匹配1个9
,也可以匹配0个9
,但是因为后面的?
表示非贪婪匹配,它就会尽可能少的匹配,结果是匹配了0个9
。
五.搜索和替换
1.分割字符串
Java 的 String 类有好几个方法是支持正则的,除了 (String regex)
外,()
也支持传入正则表达式,根据其匹配内容进行分割.
public static void main(String[] args) {
//用 \s 可以匹配一个空格字符(空格,包括 tab 即\t)
String[] ss1 = "a b c\ts".split("\\s");
String[] ss2 = "a b c".split("\\s");
String[] ss3 = "a,b ;; c".split("[\\,\\;\\s]+");
printStringArr(ss1);//{"a","b","c","s"}
printStringArr(ss2);//{"a","b","","c"}
printStringArr(ss3);//{"a","b","c"}
}
private static void printStringArr(String[] ss) {
System.out.print("{");
for (int i = 0;i < ss.length-1;i++){
System.out.print("\""+ss[i]+"\",");
}
System.out.print("\""+ss[ss.length-1]+"\""+"}");
System.out.println();
}
如果我们想让用户输入一组标签,然后把标签提取出来,因为用户的输入往往是不规范的,这时,使用合适的正则表达式,就可以消除多个空格、混合,
和;
这些不规范的输入,直接提取出规范的字符串。
2.搜索字符串
使用正则表达式还可以搜索字符串
public static void main(String[] args) {
String s = "the quick brown fox jumps over the lazy dog.";
Pattern pattern = Pattern.compile("\\wo\\w");//编译
//创建一个匹配器,该匹配器将给定的输入与此模式进行匹配
Matcher matcher = pattern.matcher(s);
while(matcher.find()){
String sub = s.substring(matcher.start(), matcher.end());
System.out.println(sub);
}
//结果:
//row
//fox
//dog
}
这里反复调用的 find()
方法,直到找不到符合条件的字符串为止,即整个字符串找完返回false为止.
找到符合要求的字符串后,则通过start()
获取开始下标,end()
获取结束下标.直到执行到下一个find()
后,又对其中的下标进行更新
3.替换字符串
1.使用 Java 中的 replace 方法
Java 中的 ()
也是支持正则的,其他替换字符串replace
方法都是支持正则.
将不规范的连续空格分隔的句子改成规范的句子:
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.
}
2.反向引用
如果我们要把搜索到的指定字符串按规则替换,比如前后各加一个<b>xxxx</b>
,这个时候,使用replaceAll()
的时候,我们传入的第二个参数可以使用$1
、$2
来反向引用匹配到的子串。例如:
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> ");
//$1 表示第一个括号里的内容
System.out.println(r);//the quick brown fox jumps <b>over</b> the <b>lazy</b> dog.
}
它实际上把任何4字符单词的前后用<b>xxxx</b>
括起来。实现替换的关键就在于" <b>$1</b> "
,它用匹配的分组子串([a-z]{4})
替换了$1
参考自廖老师的 Java教程 /wiki/1252599548343744/1255945288020320
本文用于学习记录笔记