正则表达式常常被认为是一种现用现取的工具,开发者会忽视其潜在的场景和应用。实际上,几乎所有的开发语言均对其进行了差异化的实现,基于这一点,它被广泛应用于数据校验、 数据处理等场景。 从某种意义上说,正则表达式已经不是一种现用现取的工具,而是每个开发者必须要具备的一项基础素质。
应用场景
在开发API时判断请求参数中的手机号是否合法,使用数据爬虫爬取网页中某个标签下的数据,对敏感数据进行检测与处理,采集日志时根据格式提取关键信息等,这些场景都需要使用正则表达式来提高 处理的效率。概括来说主要分为两方面:字符串格式校验、字符串提取
字符串格式校验
一次性判断一整个字符串是否符合某种格式。比如用^[0-9]+$来校验字符串是否只包含数字,用^[a-zA-Z0-9]+@gmail\.com$来校验是否是谷歌邮箱等。
字符串提取
根据某种格式从一段字符串中提取出符合要求的字串。比如使用[a-z]+从hey, welcome to 中国里提取出所有的英文单词:hey、welcome和to。
细节
元字符(Meta Characters)
正则表达式主要依赖于元字符。元字符不代表他们本身的字面意思,他们都有特殊的含义,可以类比开发语言中的关键字。一些元字符写在方括号[]中的时候有一些特殊的意思。
| 元字符 | 描述 |
|---|---|
| . | 匹配任意字符,除了换行符 |
| [ ] | 匹配方括号内的任意字符 |
| [^ ] | 匹配除了方括号里的任意字符 |
| * | 连续匹配 >=0 个重复的在 * 号之前的字符 |
| + | 连续匹配 >=1 个重复的在 + 号之前的字符 |
| { } | 连续匹配 num 个大括号之前的字符或字符集,写法包括{n,m}、{n}、{n,},分别匹配 [n,m]、n、[n,+∞) 个 |
| ? | 标记 ? 之前的字符为可选,或者惰性匹配时使用 |
| ( ) | 分组,按组匹配符合括号内正则表达式的子字符集 |
| | | 或运算符,匹配符号前或后的字符 |
| \ | 转义字符,用于匹配一些保留的字符 [ ] ( ) { } . * + ? ^ $ \ | |
| ^ | 前定界符,从第一个字符开始匹配 |
| $ | 后定界符,匹配到最后一个字符为止 |
简写字符集(Shorthand Character Sets)
一些常用的字符集可以通过简写的方式进行匹配。
| 简写 | 描述 |
|---|---|
| \w | 匹配字母、数字和下划线,等价于:[a-zA-Z0-9_] |
| \W | 匹配非字母、非数字和非下划线的字符,即符号,等价于:a-zA-Z0-9_、\w |
| \s | 匹配空格字符,包括制表符、换行符、换页符、回车符号等,等价于:[\t\n\f\r\p{Z}] |
| \S | 匹配非空格字符,等价于:\t\n\f\r\p{Z}、\s |
| \d | 匹配数字,等价于:[0-9] |
| \D | 匹配非数字:0-9、\d |
| \f | 匹配换页符 |
| \n | 匹配换行符 |
| \r | 匹配回车符 |
| \t | 匹配制表符 |
| \v | 匹配垂直制表符 |
| \p | 匹配 CR/LF(等价于 \r\n),用来匹配 DOS 行终止符 |
| . | 虽然属于元字符,但严格意义上也属于一种简写字符集 |
补充:\p{Z} 或 \p{Separator},指任何类型的空格或不可见的分隔符,极少使用,了解即可
断言(Lookarounds)
断言主要分为两种:
- 先行断言:断言部分位于某个表达式之前,用来修饰后者的判断条件,在某个子字符串满足正则部分的前提下,该子字符串紧邻的前面的字符另需满足断言部分。
- 后发断言:断言部分位于某个表达式之后,用来修饰后者的判断条件,在某个子字符串满足正则部分的前提下,该子字符串紧邻的后面的字符另需满足断言部分。
捕获与非捕获:
- 捕获:捕获文本,且对组合(同"分组"概念)进行计数。形象点说,就是会提取出匹配某个正则表达式的字符串,该表达式既可以是普通表达式,也可以是断言表达式。
- 非捕获:不捕获文本,也不针对组合进行计数。形象解释,指不会提取出匹配某个正则表达式的字符串,这个正则表达式往往属于断言表达式。
常用断言一览:
| 断言 | 类型 | 断言匹配的字符是否被捕获 | 举例 | 描述 |
|---|---|---|---|---|
| ?= | 后发断言 | 否 | a(?=bc) | 字符a后紧邻的字符串是bc |
| ?! | 后发断言 | 否 | a(?!bc) | 字符a后紧邻的字符串不是bc |
| ?<= | 先行断言 | 否 | (?<=bc)a | 字符a前紧邻的字符串是bc |
| ?<! | 先行断言 | 否 | (?<!bc)a | 字符a前紧邻的字符串不是bc |
| \b | 后发断言、先行断言 | 否 | a\b、\ba | 边界断言,指断言位置存在边界,边界:各类空格、整个待匹配字符串的头尾边界 |
| \B | 后发断言、先行断言 | 否 | a\B、\Ba | 非边界断言,与 \b 相反,指断言位置不存在边界,而是其他字符 |
| ?: | 后发断言、先行断言 | 是 | a(?:bc)、(?:bc)a | 约等价于 ?= 和 ?<= ,不同是断言匹配的字符会被捕获 |
标志(Flags)
标志也叫模式修饰符,因为它可以用来修饰表达式,进而影响匹配结果。这些标志可以任意地互相组合起来使用,也可以单独使用,
在格式上遵循 /{regex}/{flags} ,例如 /[abc]+/gm。一般情况下,不需要开发者在 regex 串中设置,而是在方法的调用入参或者调用实例的属性中设置,
例如 Java 中 java.util.regex.Pattern 类的 compile 方法,就有一种支持 flags 参数的多态化实现。
/**
* Compiles the given regular expression into a pattern with the given
* flags.
*
* @param regex
* The expression to be compiled
*
* @param flags
* Match flags, a bit mask that may include
* {@link #CASE_INSENSITIVE}, {@link #MULTILINE}, {@link #DOTALL},
* {@link #UNICODE_CASE}, {@link #CANON_EQ}, {@link #UNIX_LINES},
* {@link #LITERAL}, {@link #UNICODE_CHARACTER_CLASS}
* and {@link #COMMENTS}
*
* @return the given regular expression compiled into a pattern with the given flags
* .......
*/
public static Pattern compile(String regex, int flags) {
return new Pattern(regex, flags);
}
常见的标志有这些:
| 标志 | 描述 |
|---|---|
| i | 忽略大小写 |
| g | 全局搜索 |
| m | 多行修饰符:元字符 ^ $ 工作范围在每行的起始 |
| s | 单行修饰符 |
| x | 忽律空格符 |
| u | 忽律大小写,同时对 Unicode 编码字符集生效。不使用该标志时,默认只匹配 US-ASCII 字符集中的字符 |
| U | 启用对预定义类的 Unicode 支持 |
贪婪匹配与惰性匹配(Greedy Matching & Lazy Matching)
正则表达式默认采用贪婪匹配模式,在该模式下意味着会匹配尽可能长的子串。我们也可以使用 ? 将贪婪匹配模式转化为惰性匹配模式。两者的不同可以通过下面的例子了解:
| 输入字符串 | 正则表达式 | 捕获到的子串 |
|---|---|---|
| hahahaha | (ha)+ | hahahaha |
| hahahaha | (ha)+? | ha、ha、ha、ha |
Java 中的差异化使用
\转义字符必须替换为\\使用。- 在支持匿名捕获的基础上,还支持命名式捕获,格式如
(?<name>:asd),可以通过指定 name 的方式提取数据,类似于 K-V 数据中的 K。