外观
正则表达式笔记
约 2320 字大约 8 分钟
2025-08-16
一、正则表达式简介
1.1 什么是正则表达式?
正则表达式(Regular Expression,简称 regex 或 regexp)是一种用于匹配字符串的模式描述语言。它由一系列字符和特殊符号组成,用于定义搜索模式,广泛应用于字符串搜索、替换、验证等场景。
1.2 正则表达式的核心价值
- 文本搜索:在大量文本中快速定位特定模式
- 数据验证:验证输入是否符合特定格式(如邮箱、电话号码)
- 数据提取:从文本中提取特定信息
- 字符串替换:批量替换符合特定模式的字符串
- 日志分析:快速分析和过滤日志文件
1.3 正则表达式的基本结构
/模式/标识符- 模式:定义匹配规则的核心部分
- 斜杠:大多数语言中用作正则表达式的边界符(JavaScript、Perl等)
- 标识符:改变正则表达式行为的选项(如
g,i,m)
二、基础元字符
2.1 字符匹配
| 元字符 | 说明 | 示例 |
|---|---|---|
. | 匹配除换行符外的任意单个字符 | a.c 匹配 "abc"、"a2c" |
\d | 匹配任意数字(等同于 [0-9]) | \d\d 匹配 "42" |
\D | 匹配任意非数字 | \D\D 匹配 "ab" |
\w | 匹配字母、数字、下划线(等同于 [a-zA-Z0-9_]) | \w+ 匹配变量名 |
\W | 匹配非字母、数字、下划线 | \W 匹配标点符号 |
\s | 匹配空白字符(空格、制表符、换行符等) | a\sb 匹配 "a b" |
\S | 匹配非空白字符 | \S+ 匹配非空白单词 |
\t | 匹配制表符 | \d{3}\t\d{3} 匹配制表符分隔的数字 |
\n | 匹配换行符 | Line1\nLine2 匹配两行文本 |
2.2 字符类
| 语法 | 说明 | 示例 |
|---|---|---|
[abc] | 匹配方括号内的任意一个字符 | [aeiou] 匹配任意元音 |
[^abc] | 匹配不在方括号内的任意字符 | [^0-9] 匹配非数字 |
[a-z] | 匹配范围内的任意字符 | [A-Za-z] 匹配任意字母 |
[0-9] | 匹配数字范围 | [0-5] 匹配 0-5 的数字 |
三、量词与限定符
3.1 常用量词
| 量词 | 说明 | 示例 |
|---|---|---|
* | 匹配前面的模式 0 次或多次 | a*b 匹配 "b"、"ab"、"aaab" |
+ | 匹配前面的模式 1 次或多次 | a+b 匹配 "ab"、"aaab"(不匹配 "b") |
? | 匹配前面的模式 0 次或 1 次 | colou?r 匹配 "color" 和 "colour" |
{n} | 精确匹配 n 次 | \d{3} 匹配 3 位数字 |
{n,} | 匹配至少 n 次 | \d{3,} 匹配至少 3 位数字 |
{n,m} | 匹配 n 到 m 次 | \d{3,5} 匹配 3-5 位数字 |
3.2 贪婪与非贪婪匹配
贪婪匹配(默认):尽可能多地匹配字符
a.*b在字符串 "aabab" 中会匹配整个字符串
非贪婪匹配:在量词后加
?,尽可能少地匹配字符a.*?b在字符串 "aabab" 中会匹配 "aab" 和 "ab" 两部分
四、分组与捕获
4.1 捕获组
| 语法 | 说明 | 示例 |
|---|---|---|
(pattern) | 捕获组,保存匹配内容供后续使用 | (\d{3})-(\d{3}) |
\1, \2 | 引用前面的捕获组 | (\w+)\s+\1 匹配重复单词 |
示例:
(\d{4})-(\d{2})-(\d{2})- 匹配日期格式:2023-08-15
- 捕获组1:2023
- 捕获组2:08
- 捕获组3:15
4.2 非捕获组
(?:pattern)用途:将多个元素组合为一个单元,但不保存匹配内容
示例:
(?:https?|ftp)://\S+- 匹配 http://、https:// 或 ftp:// 开头的 URL
?:表示这是一个非捕获组
五、位置锚点
5.1 常用锚点
| 锚点 | 说明 | 示例 |
|---|---|---|
^ | 匹配字符串开头 | ^Hello 匹配以 "Hello" 开头的字符串 |
$ | 匹配字符串结尾 | world$ 匹配以 "world" 结尾的字符串 |
\b | 匹配单词边界 | \bcat\b 匹配 "cat" 但不匹配 "category" |
\B | 匹配非单词边界 | \Bcat\B 匹配 "category" 中的 "cat" |
5.2 零宽断言(Lookaround)
| 语法 | 说明 | 示例 |
|---|---|---|
(?=pattern) | 正向先行断言 | \w+(?=\.com) 匹配 ".com" 前的单词 |
(?!pattern) | 负向先行断言 | \d+(?!\.) 匹配后面不是小数点的数字 |
(?<=pattern) | 正向后行断言 | (?<=\$)\d+ 匹配 "$" 后面的数字 |
(?<!pattern) | 负向后行断言 | (?<!\d)\d{3} 匹配前面不是数字的3位数字 |
示例:
(?<=\$)\d+\.\d{2}- 匹配 "$10.99" 中的 "10.99"
(?<=\$)表示匹配 ""后面的内容,但不包含"" 本身
六、正则表达式标识符
标识符改变正则表达式的解析方式,位于表达式末尾:
| 标识符 | 说明 | 示例 |
|---|---|---|
g | 全局搜索,查找所有匹配项 | /abc/g 匹配所有 "abc" |
i | 忽略大小写 | /hello/i 匹配 "Hello"、"HELLO" |
m | 多行模式,^ 和 $ 匹配行首行尾 | /^line/m 匹配多行文本中的每行开头 |
s | 点号匹配所有字符,包括换行符 | /a.b/s 匹配 "a\nb" |
u | 启用 Unicode 匹配 | /\u{1F600}/u 匹配表情符号 |
使用示例:
// JavaScript
const regex = /pattern/gim;
// Python
import re
pattern = re.compile(r'pattern', re.IGNORECASE | re.MULTILINE)
// Java
Pattern pattern = Pattern.compile("pattern", Pattern.CASE_INSENSITIVE | Pattern.MULTILINE);七、常用正则表达式模式
7.1 常见验证模式
# 邮箱验证
^[a-zA-Z0-9._%+-]+@[a-zA-Z0-9.-]+\.[a-zA-Z]{2,}$
# 电话号码(中国)
^1[3-9]\d{9}$
# 身份证号(中国)
^[1-9]\d{5}(18|19|20)\d{2}(0[1-9]|1[0-2])(0[1-9]|[12]\d|3[01])\d{3}[\dX]$
# 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]?)$
# URL
^(https?:\/\/)?([\da-z\.-]+)\.([a-z\.]{2,6})([\/\w \.-]*)*\/?$
# 日期(YYYY-MM-DD)
^\d{4}-(0[1-9]|1[0-2])-(0[1-9]|[12][0-9]|3[01])$
# 密码(至少8位,包含大小写字母和数字)
^(?=.*[a-z])(?=.*[A-Z])(?=.*\d).{8,}$7.2 常用提取模式
# 提取 HTML 标签内容
<([a-z]+)>(.*?)<\/\1>
# 提取 JSON 键值对
"([^"]+)":\s*"([^"]*)"
# 提取 URL 参数
[?&]([^=&]+)=([^&]*)
# 提取 Markdown 标题
^(#{1,6})\s+(.+)$
# 提取 SQL SELECT 语句中的字段
SELECT\s+([\w\*,\s]+)\s+FROM八、正则表达式在不同场景的应用
8.1 在 JavaScript 中使用
// 测试匹配
const regex = /hello/i;
console.log(regex.test('Hello world')); // true
// 获取匹配结果
const str = 'The rain in SPAIN stays mainly in the plain';
const matches = str.match(/ain/gi);
console.log(matches); // ["ain", "AIN", "ain", "ain"]
// 字符串替换
const result = str.replace(/ain/gi, '***');
console.log(result); // "The r*** in SP*** st***s m***ly in the pl***"
// 字符串分割
const words = str.split(/\s+/);
console.log(words); // ["The", "rain", "in", "SPAIN", "stays", "mainly", "in", "the", "plain"]8.2 在 Python 中使用
import re
# 搜索匹配
text = "The rain in Spain"
match = re.search(r"ain", text)
if match:
print("Match found:", match.group())
# 查找所有匹配
matches = re.findall(r"ain", text)
print("All matches:", matches) # ['ain', 'ain', 'ain']
# 替换
new_text = re.sub(r"ain", "***", text)
print("Replaced text:", new_text) # The r*** in Sp*** stays m***ly in the pl***
# 编译正则表达式(提高重复使用效率)
pattern = re.compile(r"\b\w{5}\b")
five_letter_words = pattern.findall(text)
print("5-letter words:", five_letter_words)8.3 在 Nginx 配置中使用
# 匹配图片文件
location ~* \.(jpg|jpeg|png|gif)$ {
expires 30d;
add_header Cache-Control "public";
}
# 重写 URL
rewrite ^/blog/([0-9]{4})/([0-9]{2})/([0-9]{2})/(.*)$ /blog.php?year=$1&month=$2&day=$3&title=$4 last;
# 检测移动设备
if ($http_user_agent ~* (mobile|android|iphone)) {
rewrite ^/$ /mobile/ permanent;
}8.4 在 MongoDB 查询中使用
// 模糊查询(包含 "U" 的文档)
db.collection.find({name: /U/});
// 忽略大小写的模糊查询
db.collection.find({name: /urbaneh/i});
// 匹配数组中的元素
db.collection.find({tags: /java/});
// 匹配以特定字符串开头
db.collection.find({name: /^John/});
// 匹配以特定字符串结尾
db.collection.find({name: /Smith$/});九、性能优化与最佳实践
9.1 避免常见陷阱
避免过度回溯
a+b在字符串 "aaaaaaaaaaaaaaaaaaaaaac" 中会导致大量回溯
优化:使用非贪婪匹配或更精确的模式
a{1,10}b避免嵌套量词
(a+)*这种模式可能导致灾难性回溯
避免不必要的捕获组
(?:https?|ftp)://\S+使用非捕获组提高性能
9.2 性能优化技巧
锚定匹配位置
^Error:.*比
Error:.*快得多,因为直接从行首开始匹配使用原子组
(?>a+)防止回溯,提高性能
拆分复杂正则
# 复杂 ^[a-zA-Z0-9._%+-]+@[a-zA-Z0-9.-]+\.[a-zA-Z]{2,}$ # 拆分验证 if (email.includes('@')) { const [local, domain] = email.split('@'); // 分别验证 local 和 domain }预编译正则表达式
- 在 Python 中使用
re.compile() - 在 Java 中使用
Pattern.compile()
- 在 Python 中使用
9.3 调试技巧
使用在线正则表达式测试工具
逐步构建正则表达式
- 从简单模式开始,逐步添加复杂度
- 每次添加后测试是否符合预期
使用注释模式
(?x) # 启用注释模式 \d{4} # 年份 - # 分隔符 \d{2} # 月份 - # 分隔符 \d{2} # 日期
十、常见问题解决
10.1 处理特殊字符
问题:如何匹配正则中的特殊字符(如 ., *, +)?
解决方案:使用反斜杠转义
\.\*\+- 匹配字符串 ".*+"
10.2 匹配多行文本
问题:如何匹配跨多行的文本?
解决方案:
使用
s标识符使.匹配换行符/a.b/s显式匹配换行符
a[\s\S]*?b
10.3 防止过度匹配
问题:如何避免匹配到比预期更长的字符串?
解决方案:使用非贪婪匹配
"<div>.*?</div>"- 在字符串
<div>hello</div><div>world</div>中会匹配两个独立的 div
