正则表达式 基础篇
正则表达式 基础篇
mengnankkzhou简介
一个正则表达式就是一个描述规则的字符串,所以,只需要编写正确的规则,我们就可以让正则表达式引擎去判断目标字符串是否符合规则。
正则表达式是一套标准,它可以用于任何语言。Java标准库的java.util.regex
包内置了正则表达式引擎,在Java程序中使用正则表达式非常简单。
举个例子:要判断用户输入的年份是否是20##
年,我们先写出规则如下:
一共有4个字符,分别是:2
,0
,0~9任意数字
,0~9任意数字
。
对应的正则表达式就是:20\d\d
,其中\d
表示任意一个数字。
在java中就是
1 | 20\\d\\d |
注意Java字符串用\\
表示\
。
匹配规则
正则表达式的匹配规则是从左到右按规则匹配
&
正则表达式
1 | a\&c |
它能精确匹配字符串"a&c"
但不能匹配"ac"
、"a-c"
、"a&&c"
等
如果想匹配非ASCII字符,例如中文,那就用\u####
的十六进制表示,例如:a\u548cc
匹配字符串"a和c"
,中文字符和
的Unicode编码是548c
这上面是精确匹配,但是用处不大。
因为我们可以直接String.equals()即可
.
可以用.
匹配一个任意字符。
例如,正则表达式a.c
中间的.
可以匹配一个任意字符,例如,下面的字符串都可以被匹配:
"abc"
,因为.
可以匹配字符b
;"a&c"
,因为.
可以匹配字符&
;"acc"
,因为.
可以匹配字符c
。
但它不能匹配"ac"
、"a&&c"
,因为.
匹配一个字符且仅限一个字符。
\d
如果我们只想匹配0
~9
这样的数字,可以用\d
匹配。例如,正则表达式00\d
可以匹配:
"007"
,因为\d
可以匹配字符7
;"008"
,因为\d
可以匹配字符8
。
它不能匹配"00A"
,"0077"
,因为\d
仅限单个数字字符。
\w
用\w
可以匹配一个字母、数字或下划线,w的意思是word。例如,java\w
可以匹配:
"javac"
,因为\w
可以匹配英文字符c
;"java9"
,因为\w
可以匹配数字字符9
;。"java_"
,因为\w
可以匹配下划线_
。
它不能匹配"java#"
,"java "
,因为\w
不能匹配#
、空格等字符。
\s
用\s
可以匹配一个空格字符,注意空格字符不但包括空格
,还包括tab字符(在Java中用\t
表示)。例如,a\sc
可以匹配:
"a c"
,因为\s
可以匹配空格字符"a c"
,因为\s
可以匹配tab字符\t
。
它不能匹配"ac"
,"abc"
等。
\D
用\d
可以匹配一个数字,而\D
则匹配一个非数字。例如,00\D
可以匹配:
"00A"
,因为\D
可以匹配非数字字符A
;"00#"
,因为\D
可以匹配非数字字符#
。
00\d
可以匹配的字符串"007"
,"008"
等,00\D
是不能匹配的。
类似的,\W
可以匹配\w
不能匹配的字符,\S
可以匹配\s
不能匹配的字符,这几个正好是反着来的。
*
修饰符*
可以匹配任意个字符,包括0个字符。我们用A\d*
可以匹配:
A
:因为\d*
可以匹配0个数字;A0
:因为\d*
可以匹配1个数字0
;A380
:因为\d*
可以匹配多个数字380
。
+
修饰符+
可以匹配至少一个字符。我们用A\d+
可以匹配:
A0
:因为\d+
可以匹配1个数字0
;A380
:因为\d+
可以匹配多个数字380
。
但它无法匹配"A"
,因为修饰符+
要求至少一个字符。
?
修饰符?
可以匹配0个或一个字符。我们用A\d?
可以匹配:
A
:因为\d?
可以匹配0个数字;A0
:因为\d?
可以匹配1个数字0
。
{}
如果我们想精确指定n个字符怎么办?用修饰符{n}
就可以。A\d{3}
可以精确匹配:
A380
:因为\d{3}
可以匹配3个数字380
。
如果我们想指定匹配n~m个字符怎么办?用修饰符{n,m}
就可以。A\d{3,5}
可以精确匹配:
A380
:因为\d{3,5}
可以匹配3个数字380
;A3800
:因为\d{3,5}
可以匹配4个数字3800
;A38000
:因为\d{3,5}
可以匹配5个数字38000
。
如果没有上限,那么修饰符{n,}
就可以匹配至少n个字符。
练习
请编写一个正则表达式匹配国内的电话号码规则:3~4位区号加7~8位电话,中间用-连接,例如:010-12345678。
1 | ^\d{3,4}-\d{7,8}$ |
^
:表示字符串的开始。\d{3,4}
:匹配 3 到 4 位数字(区号)。-
:匹配一个连接符-
。\d{7,8}
:匹配 7 到 8 位数字(电话号码)。$
:表示字符串的结束。
复杂匹配
用正则表达式进行多行匹配时,我们用^
表示开头,$
表示结尾。例如,^A\d{3}$
,可以匹配"A001"
、"A380"
。
[…]
如果我们规定一个7~8位数字的电话号码不能以0
开头,应该怎么写匹配规则呢?\d{7,8}
是不行的,因为第一个\d
可以匹配到0
。
使用[...]
可以匹配范围内的字符,例如,[123456789]
可以匹配1
~9
,这样就可以写出上述电话号码的规则:[123456789]\d{6,7}
。
把所有字符全列出来太麻烦,[...]
还有一种写法,直接写[1-9]
就可以。
所以就是
1 | [1-9]\d{6,7} |
要匹配大小写不限的十六进制数,比如1A2b3c
,我们可以这样写:[0-9a-fA-F]
,它表示一共可以匹配以下任意范围的字符:
0-9
:字符0
~9
;a-f
:字符a
~f
;A-F
:字符A
~F
。
如果要匹配6位十六进制数,前面讲过的{n}
仍然可以继续配合使用:[0-9a-fA-F]{6}
[...]
还有一种排除法,即不包含指定范围的字符。假设我们要匹配任意字符,但不包括数字,可以写[^1-9]{3}
:
- 可以匹配
"ABC"
,因为不包含字符1
~9
; - 可以匹配
"A00"
,因为不包含字符1
~9
; - 不能匹配
"A01"
,因为包含字符1
; - 不能匹配
"A05"
,因为包含字符5
。
或匹配
用|
连接的两个正则规则是或规则,例如,AB|CD
表示可以匹配AB
或CD
现在我们想要匹配字符串learn java
、learn php
和learn go
怎么办?一个最简单的规则是learn\sjava|learn\sphp|learn\sgo
,但是这个规则太复杂了,可以把公共部分提出来,然后用(...)
把子规则括起来表示成learn\s(java|php|go)
。
上面的规则仍然不能匹配learn Java
、learn Go
这样的字符串。试修改正则,使之能匹配大写字母开头的learn Java
、learn Php
、learn Go
。这个要自己去进行修改
分组匹配
实际上(...)
还有一个重要作用,就是分组匹配。
正确的方法是用(...)
先把要提取的规则分组,把上述正则表达式变为(\d{3,4})\-(\d{6,8})
。就是先进行这个
现在我们没办法用String.matches()
这样简单的判断方法了,必须引入java.util.regex
包,用Pattern
对象匹配,匹配后获得一个Matcher
对象,如果匹配成功,就可以直接从Matcher.group(index)
返回子串:
1 | import java.util.regex.Matcher; |
运行上述代码,会得到两个匹配上的子串010
和12345678
。
1 | 12345678 |
要特别注意,Matcher.group(index)
方法的参数用1表示第一个子串,2表示第二个子串。如果我们传入0会得到什么呢?答案是010-12345678
,即整个正则匹配到的字符串。
这里面的括号就是用来进行分组的
但是反复使用String.matches()
对同一个正则表达式进行多次匹配效率较低,因为每次都会创建出一样的Pattern
对象。完全可以先创建出一个Pattern
对象,然后反复使用,就可以实现编译一次,多次匹配:
实现:
1 | import java.util.regex.Matcher; |
主要是建了一个数组,然后增强for然后匹配
使用Matcher
时,必须首先调用matches()
判断是否匹配成功,匹配成功后,才能调用group()
提取子串。