正则语法
正则表达式(regular expression)描述了一种字符串匹配的模式(pattern),可以用来校验数据的有效性、查找符合要求的文本以及对文本进行切割和替换等操作。通常缩写成“regex”。
默认区分大小写
构造正则表达式的方法和创建数学表达式的方法一样。也就是用多种元字符与运算符可以将小的表达式结合在一起来创建更大的表达式。正则表达式的组件可以是单个的字符、字符集合、字符范围、字符间的选择或者所有这些组件的任意组合。
正则表达式是由普通字符(例如字符 a 到 z)以及特殊字符(称为”元字符”)组成的文字模式。模式描述在搜索文本时要匹配的一个或多个字符串。正则表达式作为一个模板,将某个字符模式与所搜索的字符串进行匹配。
元字符
正则表达式由一些普通字符和一些元字符(metacharacters)组成。普通字符包括大小写的字母和数字,而元字符则具有特殊的含义。 匹配元字符本身请使用 \ 转意
特殊单字符
仅仅匹配单个字符的特殊符号
字符 | 描述 |
---|---|
. | 匹配除换行符 \n 之外的任何单字符 |
\d | 任意数字 |
\D | 任意非数字 |
\w | 任意字母数字下划线 |
\W | 任意非字母数字下划线 |
\s | 任意空白符 |
\S | 任意非空白符 |
\r | 回车符 |
\n | 换行符 |
\f | 换页符 |
\t | 制表符 |
\v | 垂直制表符 |
量词
符号 | 描述 | |
---|---|---|
* | 0到多次 | 与 {0,} 同意 |
+ | 1到多次 | 与 {1,} 同意 |
? | 0到1次 | 与 {0,1} 同意 |
{m} | m次 | |
{m,n} | m到n次 |
如果只是判断文本是否符合规则,则可以使用独占模式; 如果需要获取匹配的结果,则根据需要使用贪婪或非贪婪。
- 示例: | | 正则 | 文本 | 结果 | | —- | —- | —- | —- | | 贪婪模式 | a{1,3}ab | aaab | 匹配 | | 非贪婪模式 | a{1,3}?ab | | 匹配 | | 独占模式 | a{1,3}+ab | | 不匹配 |
独占模式的匹配过程:文本匹配到aaa后继续匹配正则的a,但此时文本的b显然不匹配正则的a,又因为不能回溯,所以匹配失败。
范围
符号 | 描述 |
---|---|
| | 隔开多个正则,表示满足其中任意一个就行 |
[…] | 多选一,匹配括号中任意单个字符 |
[a-z] | 匹配a到z之间任意单个元素(按ASCII表,包含a、z) |
[^…] | 取反,不能是括号中的任意单个元素 |
断言
用于匹配位置,而不是文本内容本身,这种结构就是断言。
类型 | 符号 | 描述 |
---|---|---|
单词边界 | \b | 示例: \btom 以tom开头 ;tom\b 以tom结尾 ; \btom\b 只能是tom |
行的开始/结束 | ^ | 匹配行的开始,多行模式可匹配任意行的开始(当^在方括号表达式中使用时,表示不接受该方括号表达式中的字符集合) |
$ | 匹配行的结尾,多行模式可匹配任意行结尾 | |
\A | 匹配整个文本的开头 | |
\z | 匹配整个文本的结尾(Python中使用\Z) | |
环视 | (?<=Y) | 肯定逆序环视;左边是Y;示例:(?<=\d)th 匹配左边是数字的th,如 9th |
(?<!Y) | 否定逆序环视;左边不是Y; | |
(?=Y) | 肯定顺序环视;右边是Y;示例:th(?=\d)右边是数字的th,如 th9 | |
(?!Y) | 否定顺序环视;右边不是Y; |
分组 & 引用
符号 | 描述 | |
---|---|---|
( ) | 用于分组,标记一个子表达式的开始和结束位置。子表达式可以保存为子组供后续使用 | |
(正则) | 保存子组 | |
(?:正则) | 不保存子组,性能更好 | |
(?P<分组名>正则) | 命名分组,相比于位置分组更容易辨识,不易出错。不过不是所有语言都支持 |
- 子组位置
通过左括号的位置即可确定是第几个分组。示例如下:
- 引用子组
大部分情况下,通过 “反斜杠+编号”的形式,即 \number 的方式来进行引用,而JavaScript中是通过“$+编号”的形式,如 $1
- 常见编程语言分组查找和替换的引用方式: | 编程语言 | 查找时引用方式 | 替换时引用方式 | | —- | —- | —- | | Java | \number | $number | | JavaScript | $number | $number | | Go | 官方不支持 | 官方不支持 | | Python | \number | \number | | PHP | | | | Ruby | | |
匹配规则
指正则中一些改变元字符匹配行为的方式,比如匹配时不区分英文字母大小写。
通过把模式修饰符放在整个正则前面来设置匹配规则,模式修饰符的格式是:(?模式标识)。
常见的匹配模式有如下4种:
匹配规则 | 修饰符 | 作用 |
---|---|---|
不区分大小写模式 | (?i) | 1. 使正则不区分英文字母大小写; 1. 修饰符如果在括号内,作用范围仅是这个括号内的正则; |
单行模式 | (?s) | 使其英文的点号可以匹配任何字符,包括换行;与下面的多行模式没关系。 |
多行模式 | (?m) | ^或$默认是匹配整个字符串的开头或结尾,多行模式使得他们能匹配每行的开头或结尾 |
注释模式 | (?#注释) | 正则可能很复杂,编写和阅读维护困难,添加注释方便理解 |
运算符优先等级
正则表达式从左到右进行计算,并遵循优先级顺序,这与算术表达式非常类似。
相同优先级的从左到右进行运算,不同优先级的运算先高后低。下表从最高到最低说明了各种正则表达式运算符的优先级顺序:
运算符 | 描述 |
---|---|
\ | 转义符 |
(), (?:), (?=), [] | 圆括号和方括号 |
*, +, ?, {n}, {n,}, {n,m} | 限定符 |
^, $, \任何元字符、任何字符 | 定位点和序列(即:位置和顺序) |
| | 替换,”或”操作 字符具有高于替换运算符的优先级,使得”m|food”匹配”m”或”food”。若要匹配”mood”或”food”,请使用括号创建子表达式,从而产生”(m|f)ood”。 |
Java中的正则
Java中目前还没有原生字符串
校验
Pattern类的静态方法matches
static boolean matches(String regex, CharSequence input)
提取
Pattern pattern = Pattern.compile("\\d{4}-\\d{2}");
Matcher matcher = pattern.matcher("2020-06 2021-07");
while (matcher.find()){
String s = matcher.group();
System.out.println(s);
}
替换
//方式一:
String newS = "2020-06-02 2021-07-04".replaceAll("(\\d{4})-(\\d{2})-(\\d{2})", "$1年$2月$3日");
//方式二:
Pattern pattern = Pattern.compile("(\\d{4})-(\\d{2})-(\\d{2})");
Matcher matcher = pattern.matcher("2020-06-02 2021-07-04");
String newStr = matcher.replaceAll("$1年$2月$3日");
System.out.println(newStr);
切割
Pattern pattern = Pattern.compile("\\W+");
//遍历分割结果
for (String s : pattern.split("apple,pear! orange; tea")) {
System.out.println(s);
}
//指定返回分割后的前几个
String[] strs = pattern.split("apple,pear! orange; tea", 2);
正则原理 & 优化原则
有穷自动机
有穷自动机(finite automaton)是正则引擎的具体实现。
有穷:一个系统具有有穷哥状态,不同的状态代表不同的意义;
自动机:指系统可以根据相应的条件在不同的状态下进行转移。
有穷自动机分为 DFA和NFA。
DFA
确定性有穷自动机(Deterministic finite automaton)
- 先看文本,再看正则,以文本为主导;
- 整个匹配过程字符串只看一遍,不回溯,相同字符串不会测试两次;
- 没有反向引用功能,也不支持捕获子组;
-
NFA
非确定性有穷自动机(Non-deterministic finite automaton)。
先看正则,再看文本,以正则为主导;
- 会发生回溯,字符串中同一部分可能会对比很多次;
- 支持子组和反向引用;
- 代表有 Java、Python、Ruby,perl等;
- POSIX NFA尝试所有可能的匹配,返回最长最左的匹配。
优化原则
- 测试正则性能,使用regex101等;
- 提前编译好正则;
- 尽量准确表示匹配范围;
- 提取出公共部分;
- 出现可能性大的放左边;
- 警惕嵌套子组重复;
- 避免不同分支重复匹配。