Java编程自学之路:正则


正则简介

正则表达式是什么

正则表达式(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))$

校验邮箱

邮箱一般有以下要求:

  1. 不能使用IP作为域名
  2. 字符只能是英文字母、数字、_.-;
  3. 首字符必须为英文字母或数字;
  4. 特殊符号不能连续出现;
  5. 必须包含@
  6. 域名的根域只能是字母,且至少为两个字符
//邮箱正则
^[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)$

文章作者: Semon
版权声明: 本博客所有文章除特別声明外,均采用 CC BY 4.0 许可协议。转载请注明来源 Semon !
评论
  目录