我是灼灼,一只初学Java的大一金渐层。
向往余秀华和狄兰·托马斯的疯狂,时常沉溺于将情感以诗相寄;追逐过王尔德、王小波的文字,后陷于毛姆和斯蒂芬·金不可自拔;热爱文学的浪潮,白日梦到底却总在现实里清醒;艳羡平静又极度渴盼奔跑的力量。
欢迎与我交流鸭· QQ:1517526827;
个人博客:https://blog.csdn.net/weixin_52777510?spm=1001.2101.3001.5343
正则表达式
正则表达式是一种用来匹配字符串的强有力的武器。Java内置了强大的正则表达式的支持。
内容来自廖雪峰官方Java教程~
文章目录
正则表达式简介
通过程序判断输入是否正确(比如笔者正在学习的注册功能的实现),这种方法需要为每种用例创建规则,然后用代码实现。下面是判断手机号的代码(其实也挺好使的):
boolean isValidMobileNumber(String s) {
// 是否是11位?
if (s.length() != 11) {
return false;
}
// 每一位都是0~9:
for (int i=0; i<s.length(); i++) {
char c = s.charAt(i);
if (c < '0' || c > '9') {
return false;
}
}
return true;
}
上述代码仅仅做了非常粗略的判断,并未考虑**首位数字不能为0
**等更详细的情况(用作一般情况也还行)。
除了判断手机号,还需要判断电子邮件地址、电话、邮编等等(这个奇奇怪怪的命名法看看就好,你想改也可以改的):
- boolean isValidMobileNumber(String s) { … }
- boolean isValidEmail(String s) { … }//这个如果是创建规则的话可以参考学长之前写的Java基础知识String类里的一个方法。
- boolean isValidPhoneNumber(String s) { … }
- boolean isValidZipCode(String s) { … }
- …
为每一种判断逻辑编写代码太繁琐了。更简单的方法是用正则表达式!
正则表达式可以用字符串来描述规则,并用来匹配字符串。例如,判断手机号,用正则表达式\d{11}
(这个方法其实也不能判断首位是否为零…):
boolean isValidMobileNumber(String s) {
return s.matches("\\d{11}");
}
使用正则表达式的好处:
一个正则表达式就是一个描述规则的字符串,所以,只需要编写正确的规则,我们就可以让正则表达式引擎去判断目标字符串是否符合规则。
正则表达式是一套标准,它可以用于任何语言。
Java标准库的
java.util.regex
包内置了正则表达式引擎。
例子:要判断用户输入的年份是否是20##
年,先写出规则如下:
一共有4个字符,分别是:2
,0
,0~9任意数字
,0~9任意数字
。
对应的正则表达式就是:20\d\d
,其中\d
表示任意一个数字。
把正则表达式转换为Java字符串就变成了20\\d\\d
,注意Java字符串用\\
表示\
。
最后,用正则表达式匹配一个字符串的代码如下:
public class Main {
public static void main(String[] args) {
String regex = "20\\d\\d";
System.out.println("2019".matches(regex)); // true
System.out.println("2100".matches(regex)); // false
}
}
使用正则表达式,不必编写复杂的代码来判断,只需给出一个字符串表达的正则规则即可。
小结
正则表达式是用字符串描述的一个匹配规则,使用正则表达式可以快速判断给定的字符串是否符合匹配规则。Java标准库java.util.regex
内建了正则表达式引擎。
匹配规则
正则表达式的匹配规则是从左到右按规则匹配。
使用正则表达式来做精确匹配:
- 匹配英文字母
对于正则表达式abc
来说,它只能精确地匹配字符串"abc"
;
- 匹配特殊字符
如果正则表达式有特殊字符,那就需要用
\
转义。
要注意正则表达式在Java代码中也是一个字符串(要加上双引号”“),所以,对于正则表达式a\&c
来说,对应的Java字符串是"a\\&c"
,因为\
也是Java字符串的转义字符,两个\\
实际上表示的是一个\
,一般写作名为regex;
- 匹配中文等非ASCII字符
用
\u####
的十六进制表示
例如:a\u548cc
匹配字符串"a和c"
,中文字符和
的Unicode编码是548c
。
匹配任意字符
精确匹配实际上用处不大,因为直接用String.equals()
就可以做到。大多数情况下,想要的匹配规则更多的是模糊匹配。
可以用
.
匹配一个任意字符(英文数字或特殊字符均可)
例如:
- 正则表达式
a.c
中间的.
可以匹配一个任意字符; .
匹配一个字符且仅限一个字符。
匹配数字
- 如果想匹配**
0
~9
**这样的数字,可以用\d
匹配。 \d
仅限单个数字字符。
匹配常用字符
- 用
\w
可以匹配一个字母、数字或下划线,w的意思是word。 \w
不能匹配**#
、空格**等字符。
匹配空格字符
用\s
可以匹配一个空格字符,注意空格字符不但包括空格``,
还包括tab字符(在Java中用\t
表示)。
匹配非数字
用\d
可以匹配一个数字,而\D
则匹配一个非数字。
类似的,\W
可以匹配\w
不能匹配的字符,\S
可以匹配\s
不能匹配的字符,它们正好是反着来的。
重复匹配(写作A\d修饰)
用\d
可以匹配一个数字,如果要匹配多个数字?
修饰符
*
可以匹配任意个字符,包括0个字符
修饰符
+
可以匹配至少一个字符(就是不包括0个)
修饰符+
要求至少一个字符(这里注意"A"是零个字符!)。
修饰符
?
可以匹配0个或一个字符
修饰符?
超过1个字符就不能匹配了(注意A33是两个字符!)
如果想精确指定n个字符用修饰符
{n}
。(1个数字就是一个字符)
如果想指定匹配n~m个字符用修饰符
{n,m}
如果没有上限,那么修饰符
{n,}
就可以匹配至少n个字符。
小结
单个字符的匹配规则如下:
正则表达式 | 规则 | 可以匹配 |
---|---|---|
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 |
复杂匹配规则
匹配开头和结尾
用正则表达式进行多行匹配时,用^
表示开头,$
表示结尾。
例如,^A\d{3}$
表示以A开头,以三个数字字符结尾。
匹配指定范围
如果规定一个7~8位数字的电话号码****不能以0
开头,匹配规则怎么写?
使用
[...]
可以匹配范围内的字符两种写法表示不为零的一个数:
[123456789]
- ``[1-9]`
[...]
还有一种排除法,即不包含指定范围的字符。假设要匹配任意字符,但不包括数字,可以写[^1-9]{3}
(注意这里的三个字符包括下面的A):
- 可以匹配
"ABC"
,因为不包含字符1
~9
;- 可以匹配
"A00"
,因为不包含字符1
~9
;- 不能匹配
"A01"
,因为包含字符1
;
要匹配大小写不限的十六进制数,比如1A2b3c
,可以这样写:[0-9a-fA-F]
,它表示一共可以匹配以下任意范围的字符(这6个字符表示的是一个十六进制数):
0-9
:字符0
~9
;a-f
:字符a
~f
;A-F
:字符A
~F
。
如果要匹配6位十六进制数写作[0-9a-fA-F]{6}
。
或规则匹配
用|
连接的两个正则规则是或规则,例如,AB|CD
表示可以匹配AB
或CD
。
使用括号
要匹配字符串learn java
、learn php
和learn go
?
一个最简单的规则是learn\\sjava|learn\\sphp|learn\\sgo
;
简化后可以把公共部分提出来,然后用(...)
把子规则括起来表示成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}
虽然这个正则匹配规则很简单,但是往往匹配成功后,下一步是提取区号和电话号码,分别存入数据库,如何提取匹配的子串?
可以用String
提供的indexOf()
和substring()
这些方法,但它们从正则匹配的字符串中提取子串没有通用性,下一次要提取还需要改代码。
正确的方法是:
- 用
(...)
先把要提取的规则分组(就是加上小括号隔开),把上述正则表达式变为(\d{3,4})\-(\d{6,8})
。 - 匹配后按括号提取子串:
现在无法用
String.matches()
这样简单的判断方法,必须引入java.util.regex
包,用Pattern
对象匹配,匹配后获得一个Matcher
对象,如果匹配成功,就可以直接从Matcher.group(index)
返回子串(两个子串1和2);
import 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("匹配失败!");
}
}
}
运行上述代码会得到两个匹配上的子串010
和12345678
。
特别注意,
Matcher.group(index)
方法的参数用1表示第一个子串,2表示第二个子串。如果传入0会得到010-12345678
,即整个正则匹配到的字符串。
Pattern
在前面的代码中用到的正则表达式代码是String.matches()
方法,而在分组提取的代码中用的是java.util.regex
包里面的Pattern
类和Matcher
类。实际上这两种代码本质上是一样的,因为String.matches()
方法内部调用的就是Pattern
和Matcher
类的方法。
但是反复使用String.matches()
对同一个正则表达式进行多次匹配效率较低,因为每次都会创建出一样的Pattern
对象。完全可以先创建出一个Pattern
对象,然后反复使用,就可以实现编译一次,多次匹配:
import java.util.regex.*;
public class Main {
public static void main(String[] args) {
Pattern pattern = Pattern.compile("(\\d{3,4})\\-(\\d{7,8})");
pattern.matcher("010-12345678").matches(); // true
pattern.matcher("021-123456").matches(); // true
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);
}
}
}
使用Matcher
时,必须首先调用matches()
判断是否匹配成功,匹配成功后,才能调用group()
提取子串。
利用提取子串的功能获得区号和号码两部分。
小结
正则表达式用(...)
分组可以通过Matcher
对象快速提取子串:
group(0)
表示匹配的整个字符串;group(1)
表示第1个子串,group(2)
表示第2个子串,以此类推。
非贪婪匹配
贪婪匹配的经典栗子!
给定一个字符串表示的数字,判断该数字末尾
0
的个数。例如:
"123000"
:3个0
"10100"
:2个0
"1001"
:0个0
可以很容易地写出该正则表达式:
(\d+)(0*)
,【表示至少一个数字,末尾有任意个0】Java代码如下:
import 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)); // "1230000" System.out.println("group2=" + matcher.group(2)); // "" } } }
然而打印的第二个子串是空字符串
""
。实际上,期望分组匹配结果是:
input \d+
0*
123000 “123” “000” 10100 “101” “00” 1001 “1001” “” 但实际的分组匹配结果是这样的:
input \d+
0*
123000 “123000” “” 10100 “10100” “” 1001 “1001” “” 仔细观察上述实际匹配结果,实际上它是完全合理的,因为
\d+
确实可以匹配后面任意个0
。
因为正则表达式默认使用贪婪匹配:任何一个规则,它总是尽可能多地向后匹配,因此,\d+
总是会把后面的0
包含进来。
要让\d+
尽量少匹配,让0*
尽量多匹配,就必须让\d+
使用非贪婪匹配。
在规则
\d+
后面加个?
即可表示非贪婪匹配
改写正则表达式后即可实现啦~
给定一个匹配规则,加上?
后就变成了非贪婪匹配。
!正则表达式(\d??)(9*)
,注意\d?
表示匹配0个或1个数字,后面第二个?
表示非贪婪匹配,给定字符串"9999"
,匹配到的两个子串分别是""
和"9999"
,因为对于\d?
来说,可以匹配1个9
,也可以匹配0个9
,但是因为后面的?
表示非贪婪匹配,它就会尽可能少的匹配,结果匹配了0个9
。
小结
正则表达式匹配默认使用贪婪匹配,可以使用?
表示对某一规则进行非贪婪匹配。
注意区分?
的含义:\d??
;
搜索和替换
分割字符串
使用正则表达式分割字符串可以实现更加灵活的功能。
String.split()
方法传入的是正则表达式。
看下面的代码:
"a b c".split("\\s"); // { "a", "b", "c" }在有空格的地方实现分割
"a b c".split("\\s"); // { "a", "b", "", "c" }空格也代表了一个字符
"a, b ;; c".split("[\\,\\;\\s]+"); // { "a", "b", "c" }!这个是什么意思???【出现一次以上的都会删掉,比如空格和分号】
再举一个例子:
a b c d
lines[0].split("\\s+")分割后数组为du[a] [b] [c] [d]
lines[0].split("\\s+")[2] -->取得分割后的第三个元素c
如果想让用户输入一组标签,然后把标签提取出来:
因为用户的输入往往是不规范的,使用合适的正则表达式,就可以消除多个空格、混合,
和;
这些不规范的输入,直接提取出规范的字符串。
搜索字符串
使用正则表达式还可以搜索字符串,例子:
import java.util.regex.*;
public class Main {
public static void main(String[] args) {
String s = "the quick brown fox jumps over the lazy dog.";
Pattern p = Pattern.compile("\\wo\\w");
Matcher m = p.matcher(s);
while (m.find()) {
String sub = s.substring(m.start(), m.end());
System.out.println(sub);
}
}
}
获取到Matcher
对象后,不需要调用matches()
方法(因为匹配整个串肯定返回false),而是反复调用find()
方法,在整个串中搜索能匹配上\\wo\\w
规则的子串,并打印出来。这种方式比String.indexOf()
要灵活得多,因为搜索的规则是3个字符:中间必须是o
,前后两个必须是字符[A-Za-z0-9_]
。
替换字符串
使用正则表达式替换字符串可以直接调用String.replaceAll()
,它的第一个参数是正则表达式,第二个参数是待替换的字符串。例子:
public 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
来反向引用匹配到的子串。例如:
public 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);
}
}
上述代码的运行结果是:
the quick brown fox jumps <b>over</b> the <b>lazy</b> dog.
它实际上是把任何4字符单词的前后用<b>xxxx</b>
括起来。实现替换的关键就在于" <b>$1</b> "
,它用匹配的分组子串([a-z]{4})
替换了$1
。
小结
使用正则表达式可以:
- 分割字符串:
String.split()
- 搜索子串:
Matcher.find()
- 替换字符串:
String.replaceAll()
有关Java中的正则表达式知识就到这里了!
如果对你有帮助的话不要忘记一键三连噢~
谢谢鸭~
初次编写于2021/2/18日;