什么是正则表达式
正则表达式(Regular Expression,简称 regex 或 regexp)是一种用于匹配字符串中字符模式的强大工具。它用一套简洁的语法规则,描述复杂的文本搜索、替换和提取需求,是每位开发者都应掌握的核心技能之一。
定义与用途
简单来说,正则表达式是一串字符,它定义了一个搜索模式。你可以用这个模式来:
- 验证用户输入是否符合格式(邮箱、手机号、密码强度)
- 从长文本中提取特定信息(URL、日期、数字)
- 批量替换文本中的某些内容
- 拆分字符串为有意义的片段
- 在代码编辑器中进行高级搜索
无论是前端表单验证、后端数据清洗,还是日常文本处理,正则表达式都能大幅提升效率。
历史背景
正则表达式的概念最早可追溯到 20 世纪 50 年代,由数学家 Stephen Kleene 提出。1970 年代,Ken Thompson 将其引入 Unix 系统的 ed 编辑器和 grep 工具中,从此成为程序员的标配工具。如今,几乎所有主流编程语言(JavaScript、Python、Java、Go 等)都内置了正则表达式支持。
正则表达式基础语法
正则表达式的语法虽然看起来像"天书",但核心元素并不多。掌握下面这些基础构件,你就能读懂和编写绝大多数正则表达式。
字面量字符
最基础的正则表达式就是普通字符本身,它们精确匹配自己:
hello
这个模式会匹配字符串中出现的 "hello"。
元字符
元字符是正则表达式中具有特殊含义的字符,它们是构建复杂模式的基石:
| 元字符 | 含义 |
|--------|------|
| . | 匹配除换行符外的任意单个字符 |
| * | 匹配前面的元素零次或多次 |
| + | 匹配前面的元素一次或多次 |
| ? | 匹配前面的元素零次或一次 |
| ^ | 匹配字符串的开头 |
| $ | 匹配字符串的结尾 |
| \ | 转义字符,让后面的元字符恢复字面含义 |
| \| | 或运算,匹配左边或右边的表达式 |
| ( ) | 分组,将多个元素组合成一个单元 |
| [ ] | 字符类,匹配方括号内的任意一个字符 |
| { } | 量词,指定前面元素的匹配次数 |
如果要匹配元字符本身,需要用反斜杠转义,比如 \. 匹配点号,\$ 匹配美元符号。
字符类
字符类用于匹配某一类字符,是最常用的功能之一:
| 字符类 | 含义 | 等价写法 |
|--------|------|----------|
| \d | 匹配任意数字 | [0-9] |
| \w | 匹配字母、数字、下划线 | [a-zA-Z0-9_] |
| \s | 匹配空白字符(空格、制表符、换行符等) | [ \t\n\r\f] |
| \D | 匹配非数字字符 | [^0-9] |
| \W | 匹配非单词字符 | [^a-zA-Z0-9_] |
| \S | 匹配非空白字符 | [^ \t\n\r\f] |
你也可以用方括号自定义字符类:
[aeiou]:匹配任意一个元音字母[a-z]:匹配任意小写字母[A-Z]:匹配任意大写字母[0-9]:匹配任意数字[^aeiou]:匹配非元音字母(^在方括号内表示取反)
量词
量词控制前面的元素应该出现多少次:
| 量词 | 含义 |
|------|------|
| * | 零次或多次(贪婪) |
| + | 一次或多次(贪婪) |
| ? | 零次或一次(贪婪) |
| {n} | 恰好 n 次 |
| {n,} | 至少 n 次 |
| {n,m} | n 到 m 次 |
示例:
a{3}匹配 "aaa"\d{4}匹配四位数字a{2,4}匹配 2 到 4 个 aa{2,}匹配至少 2 个 a
锚点
锚点不匹配具体字符,而是匹配位置:
| 锚点 | 含义 |
|------|------|
| ^ | 字符串开头 |
| $ | 字符串结尾 |
| \b | 单词边界 |
| \B | 非单词边界 |
示例:
^Hello匹配以 "Hello" 开头的字符串world$匹配以 "world" 结尾的字符串\bword\b匹配独立的单词 "word",不会匹配 "sword" 中的 "word"
分组与捕获
用圆括号 ( ) 可以把多个元素组合成一个整体,同时还能捕获匹配的内容供后续使用。
(ab)+
这个模式匹配 "ab" 重复一次或多次,如 "ab"、"abab"、"ababab"。
如果你只需要分组而不需要捕获,可以用非捕获分组:
(?:ab)+
(?: ) 表示只分组不捕获,性能略好,也不会占用捕获组编号。
反向引用
捕获的内容可以用 \1、\2 等在正则表达式内部引用,数字对应捕获组的顺序:
(\w+)\s+\1
这个模式匹配重复的单词,比如 "hello hello"。\1 引用第一个捕获组匹配的内容。
环视
环视(Lookaround)是一种高级特性,它匹配位置而不消耗字符,分为四种:
| 语法 | 名称 | 含义 |
|------|------|------|
| (?=...) | 正向前瞻 | 后面必须是 |
| (?!...) | 负向前瞻 | 后面必须不是 |
| (?<=...) | 正向后顾 | 前面必须是 |
| (?<!...) | 负向后顾 | 前面必须不是 |
示例:
\d+(?=元)匹配后面跟着"元"的数字\d+(?!元)匹配后面不跟着"元"的数字(?<=¥)\d+匹配前面是"¥"的数字(?<!¥)\d+匹配前面不是"¥"的数字
修饰符(flags)
修饰符改变正则表达式的匹配行为,常用的有:
| 修饰符 | 名称 | 作用 |
|--------|------|------|
| g | global | 全局匹配,找到所有匹配项而不是第一个 |
| i | ignoreCase | 忽略大小写 |
| m | multiline | 多行模式,^ 和 $ 匹配每行的开头和结尾 |
| s | dotAll | 让 . 也能匹配换行符 |
| u | unicode | Unicode 模式,正确处理 Unicode 字符 |
在 JavaScript 中,修饰符写在正则字面量的末尾:
const regex = /hello/gi;
常见场景示例
下面是一些实际开发中经常用到的正则表达式模式。
邮箱验证
^[a-zA-Z0-9._%+-]+@[a-zA-Z0-9.-]+\.[a-zA-Z]{2,}$
这个模式验证标准邮箱格式:用户名@域名.后缀。
const emailRegex = /^[a-zA-Z0-9._%+-]+@[a-zA-Z0-9.-]+\.[a-zA-Z]{2,}$/;
emailRegex.test("user@example.com"); // true
emailRegex.test("invalid-email"); // false
手机号验证
^1[3-9]\d{9}$
中国大陆手机号验证:以 1 开头,第二位是 3-9,总共 11 位数字。
const phoneRegex = /^1[3-9]\d{9}$/;
phoneRegex.test("13812345678"); // true
phoneRegex.test("12345678901"); // false
URL 匹配
https?:\/\/[^\s]+
匹配 http 或 https 开头的 URL。
const urlRegex = /https?:\/\/[^\s]+/g;
const text = "访问 https://example.com 和 http://test.org 获取更多信息";
text.match(urlRegex);
// ["https://example.com", "http://test.org"]
HTML 标签提取
<(\w+)[^>]*>([\s\S]*?)<\/\1>
匹配成对的 HTML 标签并捕获标签名和内容。
const htmlRegex = /<(\w+)[^>]*>([\s\S]*?)<\/\1>/g;
const html = "<div>Hello <b>World</b></div>";
html.match(htmlRegex);
// ["<div>Hello <b>World</b></div>", "<b>World</b>"]
密码强度校验
至少 8 位,包含大写字母、小写字母和数字:
^(?=.*[a-z])(?=.*[A-Z])(?=.*\d).{8,}$
这里用了三个正向前瞻来分别检查三种字符是否存在。
const passwordRegex = /^(?=.*[a-z])(?=.*[A-Z])(?=.*\d).{8,}$/;
passwordRegex.test("Abc12345"); // true
passwordRegex.test("abc12345"); // false(缺少大写字母)
正则表达式调试技巧
正则表达式写起来容易出错,掌握调试技巧能帮你少走弯路。
在线测试工具
写正则时,一定要配合在线测试工具实时验证:
- DevToolkit Pro 正则表达式测试器:实时匹配、语法高亮、匹配信息展示
- Regex101:详细的正则解释器
- RegExr:交互式正则学习工具
常见错误与陷阱
1. 忘记转义元字符
想匹配点号却写成了 .,结果匹配了任意字符。记得用 \. 匹配字面量点号。
2. 贪婪匹配导致过度匹配
默认情况下,* 和 + 是贪婪的,会尽可能多地匹配字符:
<.*>
用这个模式匹配 <div>Hello</div>,会匹配整个字符串而不是第一个标签。解决方法是加 ? 变成非贪婪:
<.*?>
3. 字符类中的元字符
在方括号 [ ] 内,大多数元字符会失去特殊含义。比如 [.] 匹配的就是字面量点号,而不是任意字符。
4. ^ 和 $ 的误用
^ 和 $ 默认匹配整个字符串的开头和结尾。如果想匹配每行的开头结尾,需要加上 m 修饰符。
性能优化(贪婪 vs 非贪婪)
虽然非贪婪匹配(*?、+?)很方便,但在某些场景下性能较差。对于长文本,尽量使用更精确的模式代替非贪婪:
# 性能较差
/<[^>]+>/
# 性能更好
/<[^>]+>/
另外,避免过度使用回溯(backtracking),尤其是嵌套量词的情况,可能导致灾难性回溯(catastrophic backtracking)。
使用 DevToolkit Pro 在线正则表达式测试器
写正则最头疼的就是不断试错。使用 DevToolkit Pro 的在线正则表达式测试器,你可以边写边看匹配结果,大大提升效率。
工具介绍
DevToolkit Pro 正则表达式测试器是一个功能强大的在线工具,支持:
- 实时匹配:输入正则和测试文本,立即看到匹配结果
- 语法高亮:匹配到的内容高亮显示,一目了然
- 捕获组查看:每个捕获组的内容单独展示
- 匹配信息:显示匹配数量、匹配位置等详细信息
- 代码生成:一键生成 JavaScript、Python 等语言的代码
实时匹配
在左侧输入你的正则表达式和测试文本,右侧会实时显示:
- 高亮标记所有匹配项
- 列出每个匹配的详细信息
- 展示捕获组内容
无需刷新页面,输入即所得。
flags 切换
工具顶部提供了常用修饰符的快捷开关:
- g:全局匹配
- i:忽略大小写
- m:多行模式
- s:dotAll 模式
- u:Unicode 模式
点击即可切换,实时查看效果,不用手动修改正则表达式。
工具优势
- 纯前端运行:你的正则和测试文本不会上传到服务器,保护隐私
- 零延迟响应:本地计算,匹配速度极快
- 语法参考:内置常用正则语法速查表,边写边查
- 代码导出:一键复制可用的代码片段到你的项目中
- 完全免费:所有功能免费使用,无次数限制
最佳实践
1. 从简单开始,逐步构建
不要试图一次性写出完美的正则。先写最简单的版本,然后逐步添加约束和边界条件。
2. 保持正则简洁可读
过于复杂的正则表达式是维护噩梦。如果一个正则超过 20 个字符,考虑拆分成多个步骤,或者加上注释。
一些语言支持正则注释,比如 JavaScript 的 x 修饰符(需转义空格),或者用字符串拼接:
const regex = new RegExp(
"^" +
"(?=.*[a-z])" + // 至少一个小写字母
"(?=.*[A-Z])" + // 至少一个大写字母
"(?=.*\\d)" + // 至少一个数字
".{8,}" +
"$"
);
3. 优先使用内置方法
很多常见需求(邮箱验证、URL 解析)语言或框架已经提供了内置方法。优先使用经过充分测试的标准库,而不是自己写正则。
4. 充分测试边界情况
写好正则后,一定要测试各种边界情况:
- 空字符串
- 刚好符合长度的输入
- 超出长度的输入
- 特殊字符
- 空匹配的情况
5. 考虑可读性和可维护性
能工作的正则 ≠ 好的正则。优先选择更易读、更易维护的写法,哪怕稍微长一点。
FAQ
正则表达式和通配符有什么区别?
通配符(如 *、?)通常只用于简单的文件名匹配,功能非常有限;而正则表达式是一套完整的模式匹配语言,功能强大得多,可以描述非常复杂的模式。
所有编程语言的正则表达式都一样吗?
基本语法(字符类、量词、锚点等)大致相同,但高级特性(如环视、命名捕获组)的支持程度和具体语法可能有差异。JavaScript、Python、PCRE 是比较常见的流派,使用时注意查阅对应语言的文档。
正则表达式能匹配 HTML 吗?
简单的 HTML 提取可以用正则,但对于复杂的、嵌套的 HTML 结构,正则表达式无法正确处理(因为 HTML 不是正则语言)。正确的做法是使用 HTML 解析器(如浏览器的 DOM API、Python 的 BeautifulSoup 等)。
什么是灾难性回溯?
灾难性回溯(catastrophic backtracking)是指正则表达式在某些输入下需要尝试指数级的匹配路径,导致程序卡死。通常由嵌套的量词(如 (a+)+)引起。避免方法是:尽量减少回溯的可能性,使用更精确的字符类,或者使用原子组(atomic groups)等高级特性。
在线正则测试工具安全吗?
取决于具体工具。很多在线工具会把你的数据发送到服务器处理。如果测试的是敏感数据(如用户隐私、生产环境日志),建议使用纯前端运行的工具。DevToolkit Pro 正则表达式测试器 完全在浏览器本地运行,数据不会上传,可以放心使用。
本文由 DevToolkit Pro 提供。更多开发者工具请访问 首页。