正则表达式
正则表达式的本质,不是背下一堆符号,而是用一种模式去描述“什么样的字符串算匹配”。
它非常适合做文本搜索、格式校验、提取、替换等工作。但正则真正难的地方,不在语法本身,而在于如何把一个字符串模式描述清楚。
从匹配位置开始:^ $ \b
有些时候,我们关心的不是“有没有某段文本”,而是“它出现在什么位置”。
^start:匹配以start开头的字符串end$:匹配以end结尾的字符串^JQiue$:只匹配完整的JQiuethe:只表示包含the,并不限制开头和结尾
^ 和 $ 很常见,尤其适合做“整串校验”。如果缺少它们,正则往往只是匹配了目标字符串中的某一段,而不是整个字符串。
匹配内容:字符、字符组、范围
位置解决的是“在哪”,匹配内容解决的是“匹配什么”。
最常见的写法有三类:
- 普通字符:比如
abc - 字符组:比如
[abc],表示匹配a、b、c中的任意一个 - 范围:比如
[0-9]、[a-zA-Z]
字符组适合表达“这一位可以是什么”,范围适合表达“这一位落在哪个区间里”。实际写正则时,很多模式都是先想清楚“每个位置允许出现什么字符”,再去组合。
匹配次数:* + ? {m,n}
确定了匹配内容后,下一步通常是确定它能出现几次。
abc*:c可以重复 0 次或多次abc+:c至少重复 1 次abc?:c是可选的abc{2}:c重复 2 次abc{2,3}:c重复 2 到 3 次abc{2,}:c至少重复 2 次
量词描述的是“前一个模式”出现多少次,不一定是整个字符串。理解这一点很重要,否则很容易误判正则的含义。
组合:分组、或、引用
单个模式往往不够,正则真正开始变强,是从组合开始的。
分组
(...) 可以把一段内容看成一个整体:
(abc)\d+:abc会被匹配为第一个捕获组,\d+仍然属于整个表达式(?:abc)\d+:abc只是参与匹配,但不会被单独捕获
分组的作用不只是“分段”,还包括:
- 控制量词作用范围
- 提取子串
- 配合后续引用
或
| 表示“或”,例如:
(T|t)he|car它会匹配 (T|t)he 或 car。
条件匹配
? 还可以用于条件匹配,语法为:
(?(id)yes-pattern|no-pattern)其中 id 可以是捕获组编号或名称。它表示:如果该捕获组已经匹配成功,就使用 yes-pattern,否则使用 no-pattern。
这类写法不算高频,但能说明正则不只是简单的字符拼接,它本身也带有一定的模式判断能力。
匹配策略:贪婪 vs 惰性
正则默认采用贪婪匹配,也就是在满足条件的前提下尽量匹配更长的内容。
这也是很多初学者最容易踩坑的地方。比如:
<.*>如果用它去匹配一段包含多个 HTML 标签的文本,往往会“一口吃太多”,因为 .* 会尽可能向后扩张。
如果只想让它尽量少匹配一些,就需要使用惰性量词,比如:
<.*?>*? 会把原本贪婪的 * 变成惰性匹配。它不是一次性找到“最短结果”,而是每次只尽量少匹配一点字符,只要已经能让整个表达式匹配成功,就立刻停止。
例如在下面这段文本中:
<div><span>text</span></div><.*> 往往会一直匹配到最后一个 >,而 <.*?> 通常会先停在 <div> 这里,因为走到这里时整个表达式已经成立了。
很多“为什么我这个正则匹配过头了”的问题,本质上都是贪婪匹配导致的。
修饰符:i g m
修饰符用来改变正则的匹配行为。
| 模式 | 描述 |
|---|---|
| i | 忽略大小写 |
| g | 全局搜索,匹配所有子串,而不是找到第一个后就停止 |
| m | 多行模式,使 ^ 和 $ 匹配每一行的行首和行尾 |
它们不改变正则本身的结构,但会改变匹配方式。尤其是 g 和 m,在实际使用中经常直接影响结果。
常用正则
下面这些模式适合做速查,但更重要的是要知道它们只是“特定场景下的一种写法”,不是放之四海而皆准的万能模板。
// html 注释
/<!--(.*?)-->$/
// x.y.z 格式的版本号
/^\d+(\.\d+){2}$/
// 图片链接地址
/^https?:\/\/.*?(gif|png|jpg|jpeg|webp|svg|psd|bmp|tif)$/i
// 视频链接地址
/^https?:\/\/.*?(swf|avi|flv|mpg|rm|mov|wav|asf|3gp|mkv|rmvb|mp4)$/i
// 24 小时制时间(HH:mm:ss)
/^((?:[01]\d|2[0-3]):[0-5]\d:[0-5]\d$)/
// 中文姓名
/^([\u4e00-\u9fa5·]{2,16})$/
// 英文姓名
/(^[a-zA-Z]{1}[a-zA-Z\s]{0,20}[a-zA-Z]{1}$)/
// URL 链接
/^((https?|ftp|file):\/\/)?([\da-z.-]+)\.([a-z.]{2,6})(\/\w\.-]*)*\/?/
// 手机号(基于特定号段规则)
/^1((3[\d])|(4[5,6,7,9])|(5[0-3,5-9])|(6[5-7])|(7[0-8])|(8[\d])|(9[1,8,9]))\d{8}$/
// 邮箱地址
/^[a-zA-Z0-9.!#$%&'*+\/=?^_`{|}~-]+@[a-zA-Z0-9](?:[a-zA-Z0-9-]{0,61}[a-zA-Z0-9])?(?:\.[a-zA-Z0-9](?:[a-zA-Z0-9-]{0,61}[a-zA-Z0-9])?)*$/
// 二代身份证号(18 位数字)
/^\d{6}(18|19|20)\d{2}(0\d|10|11|12)([0-2]\d|30|31)\d{3}(\d|X|x)$/
// 是否为小数
/^\d+\.\d+$/
// 是否为 HTML 标签
/<(.*)>.*<\/\1>|<(.*) \/>/
// IPv4
/^(?:(?:25[0-5]|2[0-4][0-9]|[01]?[0-9][0-9]?)\.){3}(?:25[0-5]|2[0-4][0-9]|[01]?[0-9][0-9]?)$/怎么看待这些常用模板
- 版本号、时间、IPv4 这类格式相对稳定,适合用正则表达
- 手机号这类模式依赖现实世界规则,号段变化后正则也可能过时
- 邮箱和 URL 虽然可以写出“很长很严谨”的正则,但并不意味着业务里一定值得手写到极致
- HTML 结构通常不适合靠复杂正则完整解析,能用解析器时优先用解析器
正则的常见误区
- 以为记住符号就等于会写正则。真正困难的是把字符串结构抽象成模式
- 以为匹配成功就说明写对了。很多正则只是“恰好匹配到了样例”,并没有覆盖边界情况
- 以为正则越长越严谨。过长的正则往往可读性很差,维护成本很高
- 以为正则天然适合解析复杂结构。像完整 HTML、复杂嵌套语言结构,通常不适合靠正则硬解
- 忽略贪婪匹配带来的副作用,导致结果“多吃一口”
什么时候不要用正则
正则很强,但它不是所有字符串问题的最优解。
下面这些场景,通常应该优先考虑别的方法:
- 有成熟解析器时,比如 HTML、XML、JSON
- 匹配规则会频繁变化,导致正则维护成本很高
- 业务逻辑比字符串模式更复杂时,普通代码往往更清晰
- 需要团队长期维护时,可读性有时比“一行写完”更重要
正则最适合的是“模式明确、结构相对稳定、文本处理成本高于普通代码”的场景。超出这个范围后,正则未必是收益最高的方案。
