正则表达式完全指南:语法、常用模式与实战技巧
系统讲解正则表达式的核心语法、元字符、量词、分组、断言及常用匹配模式。附带大量实用示例,助你快速掌握文本匹配与处理的利器。
正则表达式(Regular Expression,简称 Regex 或 RegExp)是一种用于描述文本模式的微型语言。无论你是做表单验证、日志分析、文本搜索替换,还是数据清洗,正则表达式都是不可或缺的工具。
虽然正则表达式的语法初看起来令人望而生畏,但只要理解了核心概念,就能很快应用到实际工作中。本文将带你从零开始,系统地掌握正则表达式。
如果你想即时测试正则表达式,推荐使用我们的 在线正则表达式测试工具,它可以实时高亮匹配结果,帮助你快速调试表达式。
1. 什么是正则表达式?
正则表达式是一个由普通字符和特殊字符(元字符)组成的字符串,用于定义一个搜索模式。这个模式可以用来:
- 匹配:判断一段文本是否符合某个模式
- 搜索:在文本中查找所有符合模式的子串
- 替换:将匹配的部分替换为新内容
- 提取:从文本中提取特定格式的数据
正则表达式广泛应用于几乎所有编程语言(JavaScript、Python、Java、Go、PHP 等)以及很多命令行工具(grep、sed、awk)。
2. 基础语法
2.1 普通字符
大多数字母和数字在正则中表示它们本身。例如正则 hello 就是匹配字符串中的文本 “hello”。
2.2 元字符
元字符是正则表达式中具有特殊含义的字符,它们是正则表达式的核心。
| 元字符 | 含义 | 示例 |
|---|---|---|
. | 匹配除换行符 \n 之外的任意单个字符 | a.c 匹配 “abc”、“a1c”、“a-c” |
\ | 转义字符,将元字符转义为普通字符 | \. 匹配字面量 ”.” |
^ | 匹配字符串的开头(或多行模式下每行的开头) | ^Hello 匹配以 “Hello” 开头的行 |
$ | 匹配字符串的结尾(或多行模式下每行的结尾) | world$ 匹配以 “world” 结尾的行 |
| ` | ` | 逻辑”或”,匹配左右两侧任意一个表达式 |
2.3 字符类(Character Classes)
字符类使用方括号 [] 定义,表示匹配其中任意一个字符。
| 表达式 | 含义 | 示例 |
|---|---|---|
[abc] | 匹配 a、b 或 c 中的任意一个 | [aeiou] 匹配任意一个小写元音 |
[a-z] | 匹配从 a 到 z 的任意一个小写字母 | [A-Za-z] 匹配任意一个英文字母 |
[0-9] | 匹配任意一个数字 | [0-9] 在多数常见场景下等价于 \d |
[^abc] | 取反,匹配不在括号内的任意字符 | [^0-9] 匹配任意非数字字符 |
注意:在字符类内部,大多数元字符会失去其特殊含义。例如
[.]匹配的是字面量 ”.”,而非任意字符。但\、]、^(仅在首位时)和-(在中间时)仍有特殊含义。
补充:在某些正则引擎的 Unicode 模式下,
\d可能匹配更多数字字符(如其他语言的数字),而[0-9]仅匹配 ASCII 数字 0–9。
2.4 预定义字符类
为了方便,正则表达式提供了常用字符类的简写形式。
| 简写 | 等价的字符类 | 含义 |
|---|---|---|
\d | [0-9] | 匹配任意数字 |
\D | [^0-9] | 匹配任意非数字字符 |
\w | [A-Za-z0-9_] | 匹配任意”词汇字符”(字母、数字、下划线) |
\W | [^A-Za-z0-9_] | 匹配任意非词汇字符 |
\s | [ \t\n\r\f\v] | 匹配任意空白字符(空格、制表符、换行等) |
\S | [^ \t\n\r\f\v] | 匹配任意非空白字符 |
\b | — | 匹配单词边界 |
\B | — | 匹配非单词边界 |
3. 量词(Quantifiers)
量词用于指定前面的字符或分组需要出现的次数。
| 量词 | 含义 | 示例 |
|---|---|---|
* | 匹配 0 次或多次 | ab*c 匹配 “ac”、“abc”、“abbc” |
+ | 匹配 1 次或多次 | ab+c 匹配 “abc”、“abbc”,但不匹配 “ac” |
? | 匹配 0 次或 1 次 | colou?r 匹配 “color” 和 “colour” |
{n} | 精确匹配 n 次 | \d{4} 匹配恰好 4 位数字 |
{n,} | 匹配至少 n 次 | \d{2,} 匹配 2 位或更多位数字 |
{n,m} | 匹配至少 n 次,至多 m 次 | \d{2,4} 匹配 2 到 4 位数字 |
3.1 贪婪与非贪婪
默认情况下,量词是贪婪的——它会尽可能多地匹配字符。在量词后加一个 ? 即可变为非贪婪(惰性)模式,尽可能少地匹配。
贪婪模式: <.+> 对 "<em>hello</em>" 匹配整个 "<em>hello</em>"
非贪婪模式:<.+?> 对 "<em>hello</em>" 匹配 "<em>" 和 "</em>"
这在处理 HTML 标签等场景时非常关键。
4. 分组与捕获
4.1 捕获分组 ()
圆括号 () 用于将多个字符组合为一个逻辑单元,并捕获匹配的内容以供后续使用。
(abc)+ 匹配一个或多个连续的 "abc"
(\d{4})-(\d{2})-(\d{2}) 匹配日期格式,分别捕获年、月、日
在替换中,可以通过 $1、$2(或 \1、\2,取决于语言)引用捕获组的内容。
4.2 非捕获分组 (?:)
有时你只需要分组但不需要捕获,可以使用 (?:) 来避免不必要的性能开销。
(?:https?|ftp):// 分组但不捕获协议部分
4.3 命名分组 (?<name>)
一些正则引擎支持命名捕获组,使捕获结果更具可读性。
(?<year>\d{4})-(?<month>\d{2})-(?<day>\d{2})
可通过 $<year> 或对应语言的 API 来引用。
5. 断言(Assertions)
断言用于指定匹配位置的条件,但不消耗字符(零宽度匹配)。
5.1 锚点断言
^:匹配字符串开头$:匹配字符串结尾\b:匹配单词边界
\bcat\b 只匹配完整单词 "cat",不匹配 "category" 中的 "cat"
5.2 前瞻断言(Lookahead)
| 语法 | 名称 | 含义 |
|---|---|---|
(?=pattern) | 正向前瞻 | 匹配后面紧跟 pattern 的位置 |
(?!pattern) | 负向前瞻 | 匹配后面不紧跟 pattern 的位置 |
\d+(?=元) 匹配后面跟着"元"的数字,如 "100元" 中的 "100"
foo(?!bar) 匹配后面不是 "bar" 的 "foo"
5.3 后瞻断言(Lookbehind)
| 语法 | 名称 | 含义 |
|---|---|---|
(?<=pattern) | 正向后瞻 | 匹配前面紧接 pattern 的位置 |
(?<!pattern) | 负向后瞻 | 匹配前面不紧接 pattern 的位置 |
(?<=\$)\d+ 匹配前面是 "$" 的数字,如 "$99" 中的 "99"
(?<!\\)\" 匹配前面没有反斜杠的双引号
注意:后瞻断言并非所有正则引擎都支持。JavaScript 从 ES2018 开始支持;Python 和 Java 均支持。部分引擎要求后瞻中的 pattern 长度固定。
6. 修饰符(Flags)
修饰符用于改变正则表达式的匹配行为。
| 修饰符 | 名称 | 含义 |
|---|---|---|
i | 忽略大小写(Case-Insensitive) | 匹配时不区分大小写 |
g | 全局匹配(Global) | 查找所有匹配项,而不是找到第一个就停止 |
m | 多行模式(Multiline) | 使 ^ 和 $ 分别匹配每行的开头和结尾,而非整个字符串 |
s | 单行模式(DotAll) | 使 . 也能匹配换行符 \n |
u | Unicode 模式 | 启用完整的 Unicode 匹配 |
在不同语言中,修饰符的使用方式略有不同:
// JavaScript
/pattern/gi
// Python
re.compile(r'pattern', re.IGNORECASE | re.MULTILINE)
7. 常用正则表达式模式
以下是一些经过验证的常用正则表达式。请注意,实际使用时应根据具体场景进行调整。
7.1 电子邮箱验证
^[a-zA-Z0-9._%+-]+@[a-zA-Z0-9.-]+\.[a-zA-Z]{2,}$
说明:匹配标准电子邮箱格式。用户名部分允许字母、数字、点号、下划线、百分号、加号和连字符;域名部分允许字母、数字、点号和连字符;顶级域名至少 2 个字母。
提示:完全符合 RFC 5322 标准的邮箱正则表达式极为复杂。上述表达式适用于大多数常见场景,但如果需要严格验证,建议结合后端验证。
7.2 URL 匹配
https?:\/\/(www\.)?[-a-zA-Z0-9@:%._\+~#=]{1,256}\.[a-zA-Z0-9()]{1,6}\b([-a-zA-Z0-9()@:%_\+.~#?&//=]*)
说明:匹配以 http:// 或 https:// 开头的 URL。
7.3 中国大陆手机号
^1[3-9]\d{9}$
说明:以数字 1 开头,第二位为 3-9,后面跟 9 位数字,总共 11 位。
7.4 身份证号码(中国大陆 18 位)
^[1-9]\d{5}(18|19|20)\d{2}(0[1-9]|1[0-2])(0[1-9]|[12]\d|3[01])\d{3}[\dXx]$
说明:匹配 18 位身份证号。依次为 6 位地区码、8 位出生日期(年月日)、3 位顺序码和 1 位校验码(数字或 X)。
7.5 IPv4 地址
^((25[0-5]|2[0-4]\d|[01]?\d\d?)\.){3}(25[0-5]|2[0-4]\d|[01]?\d\d?)$
说明:匹配标准 IPv4 地址,每段范围为 0-255。
7.6 强密码验证
^(?=.*[a-z])(?=.*[A-Z])(?=.*\d)(?=.*[@$!%*?&])[A-Za-z\d@$!%*?&]{8,}$
说明:要求至少 8 个字符,且包含至少一个小写字母、一个大写字母、一个数字和一个特殊字符。
7.7 日期格式(YYYY-MM-DD)
^\d{4}-(0[1-9]|1[0-2])-(0[1-9]|[12]\d|3[01])$
说明:匹配 YYYY-MM-DD 格式的日期。注意此正则不会验证日期的逻辑有效性(例如 2 月 30 日会通过匹配)。
7.8 十六进制颜色值
^#([0-9a-fA-F]{3}|[0-9a-fA-F]{6})$
说明:匹配 3 位或 6 位十六进制颜色值,如 #FFF 或 #FF5733。
8. 各语言中的正则表达式
8.1 JavaScript
// 创建正则表达式
const regex = /\d+/g;
// 或者
const regex2 = new RegExp('\\d+', 'g');
// 测试匹配
regex.test('hello 123'); // true
// 查找匹配
'hello 123 world 456'.match(/\d+/g); // ['123', '456']
// 替换
'hello 123'.replace(/\d+/, '***'); // 'hello ***'
// 捕获组
const match = '2026-04-19'.match(/(\d{4})-(\d{2})-(\d{2})/);
// match[1] = '2026', match[2] = '04', match[3] = '19'
8.2 Python
import re
# 测试匹配
re.search(r'\d+', 'hello 123') # 返回 Match 对象
# 查找所有匹配
re.findall(r'\d+', 'hello 123 world 456') # ['123', '456']
# 替换
re.sub(r'\d+', '***', 'hello 123') # 'hello ***'
# 捕获组
match = re.match(r'(\d{4})-(\d{2})-(\d{2})', '2026-04-19')
match.group(1) # '2026'
match.group(2) # '04'
match.group(3) # '19'
# 命名分组
match = re.match(r'(?P<year>\d{4})-(?P<month>\d{2})-(?P<day>\d{2})', '2026-04-19')
match.group('year') # '2026'
8.3 Java
import java.util.regex.*;
// 编译正则表达式
Pattern pattern = Pattern.compile("\\d+");
Matcher matcher = pattern.matcher("hello 123 world 456");
// 查找所有匹配
while (matcher.find()) {
System.out.println(matcher.group()); // 输出 "123",然后 "456"
}
// 替换
String result = "hello 123".replaceAll("\\d+", "***");
// result = "hello ***"
9. 性能优化与最佳实践
9.1 警惕回溯陷阱
正则表达式引擎在处理某些模式时可能会发生灾难性回溯(Catastrophic Backtracking),导致执行时间指数级增长。典型的危险模式包括:
(a+)+b 嵌套量词
(a|aa)+b 重叠的选择分支
(.*a){n} 带有回溯的量词组合
避免方法:
- 避免嵌套量词(如
(a+)+) - 使用原子组或占有量词(如果引擎支持)
- 尽量使表达式具有确定性
9.2 优先使用更具体的字符类
# 不推荐
.+@.+\..+
# 推荐
[A-Za-z0-9._%+-]+@[A-Za-z0-9.-]+\.[A-Za-z]{2,}
# 不推荐
.*foo.*
# 推荐
[^\r\n]*foo[^\r\n]* # 仅用于单行文本匹配
说明:当你已经知道允许出现的字符范围时,尽量用明确的字符类替代 . 或过于宽泛的 .*。这样通常更容易阅读,也能减少不必要的回溯。
9.3 合理使用锚点
使用 ^ 和 $ 可以让引擎更快地排除不匹配的情况,显著提升性能。
9.4 编译并复用正则
在需要多次使用同一正则的场景中,应提前编译并复用:
# Python:编译后复用
pattern = re.compile(r'\d{4}-\d{2}-\d{2}')
for line in lines:
if pattern.search(line):
# 处理匹配
pass
// JavaScript:在循环外创建正则
const pattern = /\d{4}-\d{2}-\d{2}/g;
9.5 不要用正则做所有事情
正则表达式并非万能。以下场景建议使用专用工具:
- 解析 HTML/XML:使用 DOM 解析器(如 Python 的 BeautifulSoup、JavaScript 的 DOMParser)
- 解析 JSON:使用
JSON.parse()等原生方法 - 复杂的语法分析:使用解析器生成器(如 ANTLR、PEG.js)
10. 在线测试工具
在编写和调试正则表达式时,可视化工具能极大提高效率。我们推荐使用在线 正则表达式测试工具,它具备以下功能:
- 实时匹配并高亮显示结果
- 支持多种修饰符(全局、忽略大小写、多行等)
- 分组捕获结果的可视化
11. 结语
正则表达式是程序员工具箱里最强大的文本处理工具之一。虽然它的语法紧凑且初学时可能令人困惑,但一旦掌握了核心概念——字符类、量词、分组和断言——你就拥有了处理几乎任何文本模式的能力。
学习的最佳方式是实践。赶快打开我们的 在线正则表达式测试工具,动手尝试本文中的每一个示例吧!