正则表达式从入门到进阶详解
正则表达式(Regular Expression,简称 Regex)是一种用于描述字符串模式的工具,可实现匹配、查找、替换、提取等字符串操作,广泛应用于文本处理、表单验证、日志分析等场景。本文将从基础概念出发,逐步深入进阶技巧,结合实例帮助你彻底掌握正则表达式。
一、正则表达式基础:构建第一个模式
1. 什么是正则表达式?
正则表达式是由普通字符(如 a、1)和元字符(如 .、*)组成的字符串,用于定义 “希望匹配的字符串规则”。例如:
- 正则 cat 可匹配字符串中所有的 “cat”(如 “cat”、“category” 中的 “cat”);
- 正则 \d{3} 可匹配任意 3 个连续数字(如 “123”、“456”)。
2. 核心基础元素
正则的基础是 “字符匹配规则”,以下是必须掌握的核心元素,按使用频率排序:
(1)普通字符:直接匹配自身
即没有特殊含义的字符,如字母(a-z、A-Z)、数字(0-9)、符号(_、-、@)等,匹配时直接对应字符串中的相同字符。
- 示例:正则 abc匹配:abc、xabcy、abc123(包含 “abc” 的字符串)不匹配:ab、abx、aBc(大小写敏感,需修饰符解决)
(2)元字符:具有特殊含义的字符
元字符是正则的 “语法核心”,需牢记常用元字符的作用:
元字符 | 含义 | 示例 | ||
. | 匹配任意单个字符(除换行符 \n) | 正则 a.b 匹配 aab、acb、a1b,不匹配 a\nb | ||
* | 匹配前面的字符0 次或多次(贪婪) | 正则 ab* 匹配 a(b 0 次)、ab(b 1 次)、abbb(b 3 次) | ||
+ | 匹配前面的字符1 次或多次(贪婪) | 正则 ab+ 匹配 ab、abbb,不匹配 a(b 0 次) | ||
? | 匹配前面的字符0 次或 1 次(贪婪) | 正则 ab? 匹配 a、ab,不匹配 abbb(b 3 次) | ||
[] | 字符集:匹配括号内任意一个字符 | 正则 a[bc]d 匹配 abd、acd,不匹配 aad | ||
[^] | 否定字符集:匹配不在括号内的任意字符 | 正则 a[^bc]d 匹配 aad、a1d,不匹配 abd | ||
` | ` | 逻辑 “或”:匹配左边或右边的模式 | 正则 `cat | dog匹配cat、dog,不匹配 bird` |
() | 分组:将多个字符视为一个整体,同时支持 “捕获” | 正则 (ab)+ 匹配 ab、abab(将 ab 视为整体重复) | ||
{n} | 匹配前面的字符恰好 n 次 | 正则 a{3} 匹配 aaa,不匹配 aa、aaaa | ||
{n,} | 匹配前面的字符至少 n 次(贪婪) | 正则 a{2,} 匹配 aa、aaa、aaaa | ||
{n,m} | 匹配前面的字符n 到 m 次(贪婪) | 正则 a{2,3} 匹配 aa、aaa,不匹配 a、aaaa |
(3)贪婪匹配 vs 非贪婪匹配
默认情况下,*、+、{n,} 等量词是贪婪的—— 即尽可能匹配更长的字符串;在量词后加 ? 可转为非贪婪(尽可能匹配更短的字符串)。
- 示例:对字符串 abbbbb 应用不同正则
正则 | 匹配结果(贪婪 / 非贪婪) | 原因 |
ab* | abbbbb(贪婪) | * 尽可能多匹配 b |
ab*? | a(非贪婪) | *? 尽可能少匹配 b(0 次) |
ab+? | ab(非贪婪) | +? 尽可能少匹配 b(1 次) |
二、正则进阶:提升匹配精准度
掌握基础后,需学习进阶语法以应对复杂场景(如边界限制、条件匹配、分组引用)。
1. 预定义字符类:简化常用字符集
正则提供了预定义的字符类,用于替代重复的字符集(如 [0-9] 可简化为 \d)。
预定义类 | 含义 | 等价字符集 |
\d | 匹配任意数字 | [0-9] |
\D | 匹配任意非数字 | [^0-9] |
\w | 匹配字母、数字、下划线 | [a-zA-Z0-9_] |
\W | 匹配非字母、数字、下划线 | [^a-zA-Z0-9_] |
\s | 匹配空白字符(空格、制表符 \t、换行符 \n 等) | [ \t\n\x0B\f\r] |
\S | 匹配非空白字符 | [^ \t\n\x0B\f\r] |
\b | 单词边界(匹配 “单词” 与 “非单词” 的分隔处,无实际字符) | - |
\B | 非单词边界 | - |
1.1 关键:\b单词边界的应用
\b 用于精准匹配 “独立单词”,避免匹配单词的一部分。例如:
- 需求:匹配 “cat”(独立单词),不匹配 “category” 中的 “cat”
- 正则:\bcat\b匹配:The cat is black 中的 “cat”不匹配:category、cat123(非独立单词)
2. 边界匹配:限制字符串的开头与结尾
^ 和 $ 用于匹配字符串的整体边界,而非中间部分,常用于 “完整字符串验证”(如手机号、邮箱)。
边界符 | 含义 | 示例 |
^ | 匹配字符串开头(多行模式下匹配 “每行开头”) | 正则 ^abc 匹配 abc123,不匹配 xabc |
$ | 匹配字符串结尾(多行模式下匹配 “每行结尾”) | 正则 abc$ 匹配 123abc,不匹配 abcx |
2.1 示例:验证手机号(11 位数字,以 1 开头)
- 正则:^1\d{10}$^1:字符串开头必须是 1;\d{10}:后面跟 10 个数字;$:字符串结尾(确保总长度 11 位,无多余字符)。
- 匹配:13800138000(符合)
- 不匹配:013800138000(开头非 1)、1380013800(仅 10 位)
3. 分组与捕获:复用匹配结果
() 除了将字符视为整体,还能 “捕获” 匹配的内容,后续可通过 “捕获组” 引用(如反向引用、提取数据)。
(1)捕获组的编号规则
捕获组按 ( 的出现顺序编号(从 1 开始,0 代表整个正则匹配结果):
- 正则:(\d{4})-(\d{2})-(\d{2})(匹配日期)组 0:整个匹配结果(如 2024-05-20)组 1:第一个 () 内容(2024,年份)组 2:第二个 () 内容(05,月份)组 3:第三个 () 内容(20,日期)
(2)非捕获组:仅分组,不捕获
若只需将字符视为整体,无需后续引用,可使用 (?:...) 定义非捕获组(节省内存,提高效率):
- 正则:(?:\d{4})-(\d{2})仅捕获组 1(\d{2},月份),(?:\d{4}) 不被捕获。
(3)反向引用:复用捕获组内容
在正则中,通过 \n(n 为捕获组编号)引用已捕获的内容,常用于匹配 “重复字符 / 单词”。
- 示例 1:匹配连续重复的数字(如 111、222)正则:(\d)\1{2}(\d):捕获 1 个数字(组 1);\1{2}:引用组 1 的内容,重复 2 次(共 3 个相同数字)。匹配:111、555,不匹配 123。
- 示例 2:匹配重复的单词(如 the the、hello hello)正则:\b(\w+)\s+\1\b(\w+):捕获 1 个单词(组 1);\s+:匹配 1 个以上空白字符;\1:引用组 1 的单词。匹配:I love love you 中的 love love。
4. 零宽断言:条件匹配(不包含在结果中)
零宽断言(Lookaround)用于判断 “匹配位置的前后是否满足特定条件”,但不将条件内容包含在匹配结果中(仅定位位置),分为 “正向” 和 “负向” 两类。
类型 | 语法 | 含义 | 示例 |
正向预查 | (?=pattern) | 匹配位置后面满足 pattern | 正则 abc(?=123) 匹配 abc123 中的 abc(后面是 123)
|
负向预查 | (?!pattern) | 匹配位置后面不满足 pattern | 正则 abc(?!123) 匹配 abc456 中的 abc(后面不是 123) |
正向后查 | (?<=pattern) | 匹配位置前面满足 pattern | 正则 (?<=123)abc 匹配 123abc 中的 abc(前面是 123) |
负向后查 | (?<!pattern) | 匹配位置前面不满足 pattern | 正则 (?<!123)abc 匹配 456abc 中的 abc(前面不是 123) |
4.1 经典场景:密码强度验证
需求:密码需满足 “8-16 位,包含字母和数字,不包含空格”。
- 正则:^(?=.*[a-zA-Z])(?=.*\d)(?!.*\s).{8,16}$^:字符串开头;(?=.*[a-zA-Z]):后面必须包含至少 1 个字母;(?=.*\d):后面必须包含至少 1 个数字;(?!.*\s):后面不能包含空白字符;.{8,16}:匹配 8-16 个任意字符(除换行);$:字符串结尾。
5. 修饰符:调整正则的匹配模式
修饰符(Flags)用于修改正则的默认行为(如大小写敏感、全局匹配),不同语言中修饰符的表示方式略有差异(如 JavaScript 用 /regex/gi,Python 用 re.compile(regex, re.I | re.S))。
修饰符 | 含义 | 示例 |
i | 不区分大小写(Case-Insensitive) | 正则 cat/i 匹配 cat、Cat、CAT |
g | 全局匹配(Global) | 正则 a/g 匹配字符串中所有的 a(而非仅第一个) |
m | 多行模式(Multi-line) | 正则 ^world/m 匹配 hello\nworld 中的 world(每行开头) |
s | 单行模式(Dotall) | 正则 . 可匹配换行符 \n(默认不匹配) |
三、实战案例:从理论到应用
以下是正则在实际开发中的高频场景,结合前文知识点解析:
1. 验证类场景
(1)验证邮箱格式
- 正则:^[a-zA-Z0-9_-]+@[a-zA-Z0-9_-]+(?:\.[a-zA-Z0-9_-]+)+$
- 解析:[a-zA-Z0-9_-]+:用户名(支持字母、数字、_、-);@:固定符号;[a-zA-Z0-9_-]+:域名主体(如 gmail、163);(?:\.[a-zA-Z0-9_-]+)+:域名后缀(如 .com、.co.uk,非捕获组)。
(2)验证 URL 格式(http/https 开头)
- 正则:^https?://(?:[a-zA-Z0-9_-]+\.)+[a-zA-Z]{2,6}(?:/[^\s]*)?$
- 解析:https?:匹配 http 或 https(? 表示 s 可选);(?:[a-zA-Z0-9_-]+\.)+:域名(如 www.baidu.);[a-zA-Z]{2,6}:顶级域名(如 com、org,长度 2-6);(?:/[^\s]*)?:可选路径(如 /index.html,非捕获组)。
2. 提取类场景
(1)提取文本中的所有手机号
- 文本:我的手机号是13800138000,备用号是13912345678
- 正则:1\d{10}(全局匹配 g)
- 结果:["13800138000", "13912345678"]
(2)提取 HTML 标签中的内容(如<title>...</title>)
- 文本:<title>正则表达式详解</title>
- 正则:<title>(.*?)</title>(非贪婪匹配,避免匹配多个标签)
- 结果:捕获组 1 为 ["正则表达式详解"]
3. 替换类场景
(1)将日期格式从 “2024-05-20” 改为 “05/20/2024”
- 正则:(\d{4})-(\d{2})-(\d{2})
- 替换为:$2/$3/$1(引用捕获组 2、3、1)
- 效果:2024-05-20 → 05/20/2024
(2)敏感词替换(将 “垃圾” 替换为 “***”)
- 文本:这个内容是垃圾
- 正则:垃圾(全局匹配 g)
- 替换为:***
- 效果:这个内容是***
四、注意事项与性能优化
- 编程语言差异:不同语言的正则引擎略有差异(如 Python 支持后向预查,JavaScript 早期不支持),需结合具体语言文档调整(如 Python 的 re 模块、JavaScript 的 RegExp 对象)。
- 避免过度回溯:尽量用具体字符集代替 .(如用 [a-zA-Z] 代替 .,减少匹配范围);避免嵌套的贪婪量词(如 (a.*)*,易导致性能问题);用非捕获组 (?:...) 代替捕获组(无需引用时)。
- 测试工具推荐:在线工具:Regex101(支持实时匹配、语法解析)、Regexr;本地工具:Sublime Text、VS Code(内置正则查找替换)。
五、学习路径总结
- 入门:掌握普通字符、核心元字符(.、*、+、[])、量词;
- 进阶:学习预定义字符类、边界匹配、分组与反向引用、零宽断言;
- 实战:通过验证、提取、替换场景练习,熟练工具使用;
- 优化:关注性能,避免过度回溯,适配不同语言引擎。
正则的核心是 “用简洁的模式描述复杂的字符串规则”,多练习、多调试是掌握的关键!
