正则简介
正则表达式是什么
正则表达式(Regular Expression)是一个用正则符号写出的公式,程序对这个公式进行语法分析,简历一个语法分析树,再根据这个分析树结合正则表达式的引擎生成执行程序(也叫状态机),用于字符匹配。
正则工具类
JDK中的java.util.regex
包提供了对正则表达式的支持。
java.util.regex
有三个核心类:
- Pattern类:正则表达式的编译表示
- Matcher类:对输入字符串进行解释和匹配操作的引擎
- PatternSyntaxException:表示正则表达式模式中的语法错误,非强制异常类
Java中使用反斜杠
\
时必须写成双斜杠\\
,用于将其转义为正常反斜杠字符。
Pattern类
Pattern
类没有公共构造方法。要创建一个Pattern
对象,必须受限调用其静态方法compile
,加载正则规则字符串,然后返回一个Pattern
对象。
Matcher类
Matcher
类可以说是java.util.regex
包的核心类,它具备三类功能:校验、查找、替换。
校验
为了校验文本是否与正则规则匹配,Matcher
提供了以下方法:
方法 | 说明 |
---|---|
public boolean lookingAt() | 从区域开头开始与输入的字符串进行匹配 |
public boolean find() | 在整个区域查找与输入的字符串进行匹配 |
public boolean find(int start) | 从区域的指定位置开始与输入的字符串进行匹配 |
public boolean matches() | 验证区域与输入的字符串是完全匹配 |
查找
为了查找文本匹配正则规则的位置,Matcher
提供了以下方法:
方法 | 说明 |
---|---|
public int start() | 返回匹配正则的字符串开始位置索引(与find()结合使用) |
public int start(int group) | 返回匹配指定group的开始位置索引 |
public int end() | 返回匹配正则的字符串结束位置索引(与find()结合使用) |
public int end(int group) | 返回匹配指定group的结束位置索引 |
public String group() | 返回匹配正则的子序列 |
public String group(int group) | 返回匹配指定group的子序列 |
替换
替换方法是替换输入字符串里文本的方法:
方法 | 说明 |
---|---|
public Matcher appendReplacement(StringBuffer sb, String replacement) | 实现非终端添加与替换 |
public StringBuffer appendTail(StringBuffer sb) | 实现终端添加和替换 |
public String replaceAll(String replacement) | 替换与给定替换字符串相匹配的输入序列的每个子序列 |
public String replaceFirst(String replacement) | 替换与给定替换字符串相匹配的输入序列的第一个子序列 |
public static String quoteReplacement(String s) | 返回指定字符串的字面替换字符串 |
元字符
元字符就是正则表达式中具有特殊意义的专用字符。
基本元字符
多选
|
表示或,当不确定要匹配的字符串,希望有多个选择时使用。分组
()
表示分组,用于表达式需要由多个子表达式组成时使用。字符有效范围
[]
限制字符有效范围,例如[a,z]
字符数量限制
{}
限制字符数量表达式 说明 {n} n是一个非负整数,表示匹配n次 {n,} n是一个非负整数,表示至少匹配n次 {n,m} n<m, n为非负整数,表示匹配n到m之间任意次 转义字符
/
用于将元字符转义为普通字符
表达式开始:
^
表达式结束:
$
非:
[^]
等价字符
顾名思义,就是对于基本元字符的一种简化。
某一类型字符的等价字符
等价字符 | 说明 |
---|---|
. |
匹配除\n 外任意单个字符 |
\d |
匹配一个数字字符,等价于[0-9] |
\D |
匹配一个分数字字符,等价于[^0-9] |
\w |
匹配任意单词字符。(Unicode字符集) |
\W |
匹配任意非单词字符 |
\s |
匹配任意不可见字符,如空格、制表符、换页符等。等价于[\f\n\r\t\v] |
\S |
匹配任意可见字符,等价于[\f\n\r\t\V] |
限制字符数量的等价字符
等价字符 | 说明 |
---|---|
* |
匹配前面的子表达式零次或多次,等价于{0,} |
+ |
匹配前面的子表达式一次或多次,等价于{1,} |
? |
匹配前面的子表达式零次或一次,等价于{0,1} |
元字符优先级
正则表达式从左向右进行计算,并遵循优先级顺序,与算术表达式类似。正则表达式优先级从高到低如下:
元字符 | 优先级 | 说明 |
---|---|---|
\ |
最高 | 转义符 |
() 、(?:) 、(?=) 、[] |
高 | 括号及中括号 |
* 、+ 、? 、{n,m} |
中 | 限定符及大括号 |
^ 、$ 、*任意字符 、任意字符* |
低 | 定位点和序列 |
` | ` | 最低 |
分组构造
所谓分组构造,是用来描述正则表达式的子表达式,用于捕获字符串中的子字符串。
捕获与非捕获
以下为分组构造中的捕获与非捕获:
表达式 | 描述 | 捕获|非捕获 |
---|---|---|
(exp) |
匹配的子表达式 | 捕获 |
(?<name>exp) |
命名的反向引用 | 捕获 |
(?:exp) |
非捕获组 | 非捕获 |
(?=exp) |
零宽度正预测先行断言 | 非捕获 |
(?!exp) |
零宽度负预测先行断言 | 非捕获 |
(?<=exp) |
零宽度正回顾后发断言 | 非捕获 |
(?<!exp) |
零宽度负回顾后发断言 | 非捕获 |
反向引用
带编号的反向引用使用以下语法:\编号
命名的反向引用通过以下语法:\k<name>
非捕获组
(?:exp)
表示当一个限定符应用到一个组,但组捕获的子字符串并非所需时,通常会使用非捕获组构造。
零宽断言
用于查找某些内容之前或之后的东西,用于指定一个位置,该位置需要满足一定的条件(断言),因此称之为零宽断言。
表达式 | 说明 |
---|---|
(?=exp) |
匹配exp前面的位置 |
(?<=exp) |
匹配exp后面的位置 |
(?!exp) |
匹配后面跟的不是exp的位置 |
(?<!exp) |
匹配前面不是exp的位置 |
正则性能优化
目前正则表达式引擎方式有两种:DFA自动机(Deterministic Final Automata 确定有限状态自动机)和NFA自动机(Non deterministic Finite Automaton 非确定有限状态自动机)。构造DFA自动机代价远大于NFA自动机,但DFA自动机执行效率高于NFA。NFA 自动机的优势是支持更多功能。例如,捕获 group、环视、占有优先量词等高级功能。这些功能都是基于子表达式独立进行匹配,因此在编程语言里,使用的正则表达式库都是基于 NFA 实现的。
NFA自动机回溯
用 NFA 自动机实现的比较复杂的正则表达式,在匹配过程中经常会引起回溯问题。大量的回溯会长时间地占用 CPU,从而带来系统性能开销。
text=“abbc”
regex=“ab{1,3}c”
这个例子匹配目的是:匹配以 a 开头,以 c 结尾,中间有 1-3 个 b 字符的字符串。NFA 自动机对其解析的过程是这样的:
- 读取正则表达式第一个匹配符 a 和字符串第一个字符 a 进行比较,a 对 a,匹配。
- 然后,读取正则表达式第二个匹配符
b{1,3}
和字符串的第二个字符 b 进行比较,匹配。但因为b{1,3}
表示 1-3 个 b 字符串,NFA 自动机又具有贪婪特性,所以此时不会继续读取正则表达式的下一个匹配符,而是依旧使用b{1,3}
和字符串的第三个字符 b 进行比较,结果还是匹配。 - 接着继续使用
b{1,3}
和字符串的第四个字符 c 进行比较,发现不匹配了,此时就会发生回溯,已经读取的字符串第四个字符 c 将被吐出去,指针回到第三个字符 b 的位置。 - 那么发生回溯以后,匹配过程怎么继续呢?程序会读取正则表达式的下一个匹配符 c,和字符串中的第四个字符 c 进行比较,结果匹配,结束。
如何避免回溯
正则表达式在进行匹配时,存在以下三种匹配模式:
贪婪模式(Greedy)
顾名思义,就是在数量匹配中,如果单独使用
+
、?
、*
、{n,m}
等量词时,正则表达式会匹配尽可能多的内容。懒惰模式(Reluctant)
在该模式下,正则表达式会尽可能少的重复匹配字符,如果成功,则继续匹配剩余字符。通过在已有表达式后添加
?
来启用懒惰模式。独占模式(Possessive)
同贪婪模式一样,独占模式一样会最大限度匹配更多内容。不同的是,匹配失败就会结束匹配,不会发生回溯问题。通过在已有表达式后添加
+
来启用独占模式。
正则表达式优化
少用贪婪模式,多用独占模式
通过独占模式来规避回溯问题。
减少分支选择
分支选择会降低正则表达式性能,可以通过以下几种方式来优化:
- 常用的选择项放在前面,使其较快地被匹配
- 尝试提取多个分支中的共用模式,减少匹配次数
减少捕获嵌套
减少不需要获取的分组,可以提高正则表达式性能。
元字符字典
限定符
字符 | 描述 |
---|---|
* |
匹配前面的子表达式零次或多次。例如,zo* 能匹配 “z” 以及 “zoo”。* 等价于{0,}。 |
+ |
匹配前面的子表达式一次或多次。例如,’zo+’ 能匹配 “zo” 以及 “zoo”,但不能匹配 “z”。+ 等价于 {1,}。 |
? |
匹配前面的子表达式零次或一次。例如,”do(es)?” 可以匹配 “do” 或 “does” 中的”do” 。? 等价于 {0,1}。 |
{n} |
n 是一个非负整数。匹配确定的 n 次。例如,’o{2}’ 不能匹配 “Bob” 中的 ‘o’,但是能匹配 “food” 中的两个 o。 |
{n,} |
n 是一个非负整数。至少匹配 n 次。例如,’o{2,}’ 不能匹配 “Bob” 中的 ‘o’,但能匹配 “foooood” 中的所有 o。’o{1,}’ 等价于 ‘o+’。’o{0,}’ 则等价于 ‘o*’。 |
{n,m} |
m 和 n 均为非负整数,其中 n <= m。最少匹配 n 次且最多匹配 m 次。例如,”o{1,3}” 将匹配 “fooooood” 中的前三个 o。’o{0,1}’ 等价于 ‘o?’。请注意在逗号和两个数之间不能有空格。 |
定位符
字符 | 描述 |
---|---|
^ |
匹配输入字符串开始的位置。如果设置了 RegExp 对象的 Multiline 属性,^ 还会与 \n 或 \r 之后的位置匹配。 |
$ |
匹配输入字符串结尾的位置。如果设置了 RegExp 对象的 Multiline 属性,$ 还会与 \n 或 \r 之前的位置匹配。 |
\b |
匹配一个字边界,即字与空格间的位置。 |
\B |
非字边界匹配。 |
非打印字符
字符 | 描述 |
---|---|
\cx |
匹配由 x 指明的控制字符。例如, \cM 匹配一个 Control-M 或回车符。x 的值必须为 A-Z 或 a-z 之一。否则,将 c 视为一个原义的 ‘c’ 字符。 |
\f |
匹配一个换页符。等价于 \x0c 和 \cL。 |
\n |
匹配一个换行符。等价于 \x0a 和 \cJ。 |
\r |
匹配一个回车符。等价于 \x0d 和 \cM。 |
\s |
匹配任何空白字符,包括空格、制表符、换页符等等。等价于 [ \f\n\r\t\v]。 |
\S |
匹配任何非空白字符。等价于 [ \f\n\r\t\v]。 |
\t |
匹配一个制表符。等价于 \x09 和 \cI。 |
\v |
匹配一个垂直制表符。等价于 \x0b 和 \cK。 |
分组
表达式 | 描述 |
---|---|
(exp) |
匹配的子表达式。()中的内容就是子表达式。 |
(?<name>exp) |
命名的子表达式(反向引用)。 |
(?:exp) |
非捕获组,表示当一个限定符应用到一个组,但组捕获的子字符串并非所需时,通常会使用非捕获组构造。 |
(?=exp) |
匹配 exp 前面的位置。 |
(?<=exp) |
匹配 exp 后面的位置。 |
(?!exp) |
匹配后面跟的不是 exp 的位置。 |
(?<!exp) |
匹配前面不是 exp 的位置。 |
特殊符号
字符 | 描述 |
---|---|
\ |
将下一个字符标记为或特殊字符、或原义字符、或向后引用、或八进制转义符。例如, ‘n’ 匹配字符 ‘n’。’\n’ 匹配换行符。序列 ‘' 匹配 “”,而 ‘(‘ 则匹配 “(“。 |
| |
指明两项之间的一个选择。 |
[] |
匹配方括号范围内的任意一个字符。形式如:[xyz]、[^xyz]、[a-z]、[^a-z]、[x,y,z] |
常用案例
校验中文
校验字符串中只能有中文字符(不包括中文标点符号)。中文字符的 Unicode 编码范围是 \u4e00
到 \u9fa5
。
^[\u4e00-\uu9fa5]$
校验身份证号码
身份证为 15 位或 18 位。15 位是第一代身份证。从 1999 年 10 月 1 日起,全国实行公民身份证号码制度,居民身份证编号由原 15 位升至 18 位。
- 15 位身份证:由 15 位数字组成。排列顺序从左至右依次为:六位数字地区码;六位数字出生日期;三位顺序号,其中 15 位男为单数,女为双数。
- 18 位身份证:由十七位数字本体码和一位数字校验码组成。排列顺序从左至右依次为:六位数字地区码;八位数字出生日期;三位数字顺序码和一位数字校验码(也可能是 X)。
// 15位有效身份证
^((1[1-5]|2[1-3]|3[1-7]|4[1-3]|5[0-4]|6[1-5])\d{4})((\d{2}((0[13578]|1[02])(0[1-9]|[12]\d|3[01])|(0[13456789]|1[012])(0[1-9]|[12]\d|30)|02(0[1-9]|1\d|2[0-8])))|([02468][048]|[13579][26])0229)(\d{3})$
// 18位有效身份证
^((1[1-5]|2[1-3]|3[1-7]|4[1-3]|5[0-4]|6[1-5])\d{4})((\d{4}((0[13578]|1[02])(0[1-9]|[12]\d|3[01])|(0[13456789]|1[012])(0[1-9]|[12]\d|30)|02(0[1-9]|1\d|2[0-8])))|([02468][048]|[13579][26])0229)(\d{3}(\d|X))$
校验邮箱
邮箱一般有以下要求:
- 不能使用IP作为域名
- 字符只能是英文字母、数字、
_
、.
、-
; - 首字符必须为英文字母或数字;
- 特殊符号不能连续出现;
- 必须包含
@
- 域名的根域只能是字母,且至少为两个字符
//邮箱正则
^[A-Za-z0-9](([_\.\-]?[a-zA-Z0-9]+)*)@([A-Za-z0-9]+)(([\.\-]?[a-zA-Z0-9]+)*)\.([A-Za-z]{2,})$
校验URL
校验 URL。支持 http、https、ftp、ftps。
//校验URL
^(ht|f)(tp|tps)\://[a-zA-Z0-9\-\.]+\.([a-zA-Z]{2,3})?(/\S*)?$
校验时间/日期
- 时间:时分秒必须是有效数字,十位不足补零;
- 日期:校验闰年2月最后一天,大小月最后一天;
//时间
^([0-1][0-9]|[2][0-3]):([0-5][0-9])$
//日期
^(?:(?!0000)[0-9]{4}([-/.]?)(?:(?:0?[1-9]|1[0-2])\1(?:0?[1-9]|1[0-9]|2[0-8])|(?:0?[13-9]|1[0-2])\1(?:29|30)|(?:0?[13578]|1[02])\1(?:31))|(?:[0-9]{2}(?:0[48]|[2468][048]|[13579][26])|(?:0[48]|[2468][048]|[13579][26])00)([-/.]?)0?2\2(?:29))$
校验中国手机
十一位数字,或者带国际区号13位数字;
移动有 16 个号段:134、135、136、137、138、139、147、150、151、152、157、158、159、182、187、188。其中 147、157、188 是 3G 号段,其他都是 2G 号段。联通有 7 种号段:130、131、132、155、156、185、186。其中 186 是 3G(WCDMA)号段,其余为 2G 号段。电信有 4 个号段:133、153、180、189。其中 189 是 3G 号段(CDMA2000),133 号段主要用作无线网卡号。总结:13 开头手机号 0-9;15 开头手机号 0-3、5-9;18 开头手机号 0、2、5-9。
此外,中国在国际上的区号为 86,所以手机号开头有+86、86 也是合法的。
//手机号码
^((\+)?86\s*)?((13[0-9])|(15([0-3]|[5-9]))|(18[0,2,5-9]))\d{8}$
中国固话
固话号码,必须加区号(以 0 开头)。
3 位有效区号:010、020~029,固话位数为 8 位。
4 位有效区号:03xx 开头到 09xx,固话位数为 7。
//中国固话号码
^(010|02[0-9])(\s|-)\d{8}|(0[3-9]\d{2})(\s|-)\d{7}$
校验IP地址
IPv4 地址通常用“点分十进制”表示成(a.b.c.d)的形式,其中,a,b,c,d 都是 0~255 之间的十进制整数
IPv6 的 128 位地址通常写成 8 组,每组为四个十六进制数的形式。
//IPv4
^([01]?\d\d?|2[0-4]\d|25[0-5])\.([01]?\d\d?|2[0-4]\d|25[0-5])\.([01]?\d\d?|2[0-4]\d|25[0-5])\.([01]?\d\d?|2[0-4]\d|25[0-5])$
//IPv6
(([0-9a-fA-F]{1,4}:){7,7}[0-9a-fA-F]{1,4}|([0-9a-fA-F]{1,4}:){1,7}:|([0-9a-fA-F]{1,4}:){1,6}:[0-9a-fA-F]{1,4}|([0-9a-fA-F]{1,4}:){1,5}(:[0-9a-fA-F]{1,4}){1,2}|([0-9a-fA-F]{1,4}:){1,4}(:[0-9a-fA-F]{1,4}){1,3}|([0-9a-fA-F]{1,4}:){1,3}(:[0-9a-fA-F]{1,4}){1,4}|([0-9a-fA-F]{1,4}:){1,2}(:[0-9a-fA-F]{1,4}){1,5}|[0-9a-fA-F]{1,4}:((:[0-9a-fA-F]{1,4}){1,6})|:((:[0-9a-fA-F]{1,4}){1,7}|:)|fe80:(:[0-9a-fA-F]{0,4}){0,4}%[0-9a-zA-Z]{1,}|::(ffff(:0{1,4}){0,1}:){0,1}((25[0-5]|(2[0-4]|1{0,1}[0-9]){0,1}[0-9])\.){3,3}(25[0-5]|(2[0-4]|1{0,1}[0-9]){0,1}[0-9])|([0-9a-fA-F]{1,4}:){1,4}:((25[0-5]|(2[0-4]|1{0,1}[0-9]){0,1}[0-9])\.){3,3}(25[0-5]|(2[0-4]|1{0,1}[0-9]){0,1}[0-9]))
特定字符
- 匹配长度为 3 的字符串:
^.{3}$
。 - 匹配由 26 个英文字母组成的字符串:
^[A-Za-z]+$
。 - 匹配由 26 个大写英文字母组成的字符串:
^[A-Z]+$
。 - 匹配由 26 个小写英文字母组成的字符串:
^[a-z]+$
。 - 匹配由数字和 26 个英文字母组成的字符串:
^[A-Za-z0-9]+$
。 - 匹配由数字、26 个英文字母或者下划线组成的字符串:
^\w+$
特定数字
- 匹配正整数:
^[1-9]\d*$
- 匹配负整数:
^-[1-9]\d*$
- 匹配整数:
^(-?[1-9]\d*)|0$
- 匹配正浮点数:
^[1-9]\d*\.\d+|0\.\d+$
- 匹配负浮点数:
^-([1-9]\d*\.\d*|0\.\d*[1-9]\d*)$
- 匹配浮点数:
^-?([1-9]\d*\.\d*|0\.\d*[1-9]\d*|0?\.0+|0)$