你是否曾在编程论坛上看到一串密密麻麻的符号如 /^[a-zA-Z0-9._%+-]+@[a-zA-Z0-9.-]+\.[a-zA-Z]{2,}$/,感到既神秘又敬畏?那就是正则表达式(Regular Expression,简称 Regex)。尽管它初看起来如同密码,却是每一位开发者、数据分析师、内容编辑都应该至少深入理解一次的强大工具。本文将带你从历史背景、核心概念、常用模式,一直讲到实战应用,让你从"看不懂"进阶到"能写能用"。
一、正则表达式简史与核心概念
正则表达式从何而来?
在 1950 年代,数学家 Stephen Kleene 在形式语言理论的研究中引入了"正规语言"(Regular Language)的概念。到了 1970 年代,Unix 工具 sed 和 grep 首次将正则表达式应用于实际的文字搜寻与替换,至此,Regex 便成为了 Unix 文化的一部分。如今,无论是 Perl、Python、JavaScript、PHP、Java 还是 C++,几乎所有主流程序语言都内建或支持正则表达式,成为了跨领域的通用技能。
什么是正则表达式?
简单来说,正则表达式是一种用符号定义文字模式的方式。它不是一门语言本身,而是一种模式描述法。给定一段文本和一个正则表达式,引擎会告诉你:"这段文本中哪些部分符合这个模式?"或"用这个模式替换成那个模式"。
例如,如果你想找出一篇文章中所有"单独成行的数字"(例如年份、编号),用正则表达式就能简洁地表达——比起写一堆 if-else 和 for 循环,Regex 通常是更优雅的选择。
二、基础语法:逐个符号的深度讲解
字符集与量词
正则表达式的核心是字符集和量词的组合。以下是最常用的几种:
字符集
.:匹配任意单个字符(除了换行符,某些引擎可设定包含换行)[abc]:字符类,匹配 a、b 或 c 中的任一个[a-z]:范围,匹配小写字母 a 到 z[^abc]:否定字符类,匹配除了 a、b、c 之外的任何字符\d:匹配任意数字(等同[0-9])\D:匹配非数字字符\w:匹配单词字符(字母、数字、下划线,等同[a-zA-Z0-9_])\W:匹配非单词字符\s:匹配任意空白字符(空格、制表符、换行符)\S:匹配非空白字符
量词
*:0 个或多个(贪心量词)+:1 个或多个(贪心量词)?:0 个或 1 个(可选){n}:恰好 n 个{n,}:至少 n 个{n,m}:至少 n 个,至多 m 个*?、+?、??:非贪心版本(懒惰量词)
锚点
^:行起始位置$:行结束位置\b:单词边界(字符与非字符之间的位置)\B:非单词边界
逃脱特殊字符
某些符号在 Regex 中有特殊含义(如 .、*、+、?、[、]、(、) 等)。若想匹配字面上的这些符号,需要在前面加上反斜线 \ 进行逃脱。例如,要匹配句号 . 的字面意义,应写成 \.。
三、常用模式实例:从简到繁
电邮地址验证
一个简化但实用的电邮正则表达式:
^[a-zA-Z0-9._%+-]+@[a-zA-Z0-9.-]+\.[a-zA-Z]{2,}$
解读:
^与$:确保模式从头匹配到尾[a-zA-Z0-9._%+-]+:帐号部分,一个或多个合法字符@:字面的 @ 符号[a-zA-Z0-9.-]+:域名的主要部分\.:字面的点[a-zA-Z]{2,}$:至少 2 个字母的顶级域名(.com、.tw、.org 等)
电话号码格式
匹配台湾手机号码(09xx-xxxx-xxxx 格式):
^09\d{2}-\d{4}-\d{4}$
IP 地址(IPv4)
匹配任意有效的 IPv4 位址:
^(\d{1,3}\.){3}\d{1,3}$
注意:这个简化版本不会检查每个八位组是否在 0-255 范围内,完整版本会更复杂。
日期格式(YYYY-MM-DD)
^\d{4}-\d{2}-\d{2}$
URL 验证
一个基本的 URL 正则表达式:
^https?://[a-zA-Z0-9.-]+\.[a-zA-Z]{2,}(/.*)?$
四、进阶技巧:捕获与回溯参考
捕获群组
圆括号不只用来分组,它还定义了捕获群组,可以在匹配后存取并重复使用。例如:
(\d{4})-(\d{2})-(\d{2})
匹配日期时,会捕获三个群组:第 1 组是年份、第 2 组是月份、第 3 组是日期。在替换操作中,可以用 $1、$2、$3 引用这些群组。
非捕获群组
有时你只想分组但不需要捕获,可以使用 (?:...) 语法:
(?:cat|dog) ate
这会匹配 "cat ate" 或 "dog ate",但不会占用捕获槽位。
向前查看与向后查看
有时需要匹配"前面或后面是某某"的位置,而不消耗那些字符。例如,匹配"后面跟着 'ing'"的单词:
\w+(?=ing)
或者匹配"前面是 'price: '"的数字:
(?<=price: )\d+
五、常见误区与性能陷阱
贪心量词 vs 懒惰量词
预设情况下,量词如 * 和 + 是"贪心"的,它们会匹配尽可能多的字符。考虑以下例子:
const regex = /<.*>/;
const text = '<div>content</div>';
预期是匹配 <div>,但贪心的 .* 实际上会匹配 <div>content</div>——整个字串!若要修正,改用懒惰量词 .*? 即可。
特殊字符忘记逃脱
许多初学者在撰写 Regex 时忘记逃脱句号、括号等特殊字符,导致模式不符预期。记住:若要匹配字面的特殊字符,务必加上 \。
效能灾难:重复的量词与回溯
某些欠佳设计的正则表达式会导致"灾难性回溯"(Catastrophic Backtracking),使得引擎花费指数级时间去尝试各种匹配组合。例如:
(a+)+b
若输入是一长串 'a' 但没有 'b',引擎会在每一步都尝试回溯,导致性能剧降。避免这类模式的秘诀是:尽量限制量词的范围,使用非贪心量词,或者明确加上边界条件。
六、实务应用范例
验证电话号码
在 Web 表单中验证用户输入的电话号码:
function validatePhone(phoneNumber) {
const regex = /^09\d{2}-\d{4}-\d{4}$/;
return regex.test(phoneNumber);
}
console.log(validatePhone("0912-3456-7890")); // true
console.log(validatePhone("12345")); // false
从文本中提取电子邮件
const text = "联系我:[email protected] 或 [email protected]";
const emailRegex = /[a-zA-Z0-9._%+-]+@[a-zA-Z0-9.-]+\.[a-zA-Z]{2,}/g;
const emails = text.match(emailRegex);
console.log(emails); // ["[email protected]", "[email protected]"]
替换与格式化
将日期格式从 YYYY-MM-DD 改为 DD/MM/YYYY:
const date = "2026-03-18";
const reformatted = date.replace(/(\d{4})-(\d{2})-(\d{2})/, "$3/$2/$1");
console.log(reformatted); // "18/03/2026"
去除 HTML 标签
虽然用 Regex 去除 HTML 标签不是最佳实践(应该用 HTML 解析器),但简单示例如下:
const html = "<p>这是一段 <strong>重要</strong> 文字。</p>";
const plainText = html.replace(/<[^>]+>/g, "");
console.log(plainText); // "这是一段 重要 文字。"
七、工具与线上资源
掌握 Regex 最好的方式就是不断练习。使用正则表达式生成器工具可以实时测试你的模式,看看它如何匹配各种输入。同时,也推荐查阅以下资源:
- MDN Web Docs:JavaScript RegExp 的详细文件
- Regex101.com:线上 Regex 测试工具,支持多种语言与风格
- RegexPal:另一个优秀的线上测试平台
- Regex Cheat Sheet:快速速查表
此外,不同的程序语言和工具可能在 Regex 实现上有细微差异。例如,Java 和 JavaScript 的量词语法相同,但 Perl 支持一些更高级的特性。在实际应用中,熟悉你所用语言的 Regex 方言很重要。
八、总结与进阶方向
正则表达式是一项初看复杂、但一旦掌握就能大幅提升开发效率的技能。核心要点是:
- 理解字符集、量词、锚点的组合逻辑
- 透过实际例子(电邮、电话、日期等)建立直觉
- 留意性能陷阱,特别是贪心量词与回溯问题
- 在实务中频繁测试与迭代,不要过度最佳化
若要进一步深化,可探索 PCRE(Perl Compatible Regular Expressions)、命名捕获群组、更复杂的向前/向后查看,甚至撰写自己的 Regex 引擎以理解其内部运作。但对大多数日常编程任务而言,本文涵盖的内容已足敷应用。
现在,打开正则表达式生成器工具,试着编写几个模式来匹配你自己的数据集吧。实践是最好的老师。