正则不是"一个功能",而是一种描述文本模式的微型语言

很多人把正则表达式当成"查找工具里的一个开关",但更准确的理解是:它是一套用少量符号描述"文本长什么样"的规则语言。比如 `\d{4}-\d{2}-\d{2}` 描述的是"四位数字-两位数字-两位数字"这种形状,而不是某个具体日期;`^[a-zA-Z0-9._%+-]+@[a-zA-Z0-9.-]+\.[a-zA-Z]{2,}$` 描述的是"本地名@域名"这种结构。正因为它是"形状描述"而非"具体值",同一段写法才能复用到邮箱、日志、身份证、URL 等完全不同的场景。掌握它的关键,不在于背下每个符号,而在于理解三件事:字符怎么表示(字符类、转义)、重复怎么表示(量词、贪婪/懒惰)、结构怎么分组与锚定(分组、断言、^ $)。本工具在浏览器本地用 ECMAScript 引擎构建并运行正则,下面几张表能帮你避开最常见的坑。

正则引擎家族:为什么同一段写法在不同地方结果不同

同样一段正则,在浏览器、PHP、Java、Python 里可能"能跑 / 报错 / 结果不同",根子在于底层引擎实现不一样。主流引擎分两类:回溯型 NFA(大多数语言默认)和 DFA/自动机(RE2 等,线性时间、永不回溯)。下表按国内最常用的运行环境做了对照(依据 ECMA-262 第 15 版 / ES2024、PCRE2、Python re、Java java.util.regex 官方文档)。

引擎 / 实现代表使用场景回溯行为关键语法差异
ECMAScript(浏览器 / Node.js,本工具所基于)前端校验、JS 脚本回溯型 NFA;默认贪婪,支持懒惰 *?,不支持占有量词支持 \p{...} 需开 u 标志;命名组 (?<name>...);u 模式下 \d 匹配全部 Unicode 数字
PCRE2(PHP、Notepad++、Apache 等)服务端、编辑器回溯型;支持占有量词 *+ 与原子组 (>...),可缓解 ReDoS语法最丰富,支持 \p{L}、回溯控制 (*SKIP)
RE2(Go regexp、Rust 部分库)高并发服务端DFA / 自动机,线性时间,永不回溯,天然抗 ReDoS不支持反向引用、无后行断言;仅为语法子集
Python re(CPython 默认)数据清洗、爬虫回溯型;不支持占有量词;3.11 起有原子分组实验特性支持 \p 需第三方 regex 库;默认 . 不匹配换行
Java java.util.regex企业后端回溯型;支持占有量词 *+、原子组 (>...)支持 \p{L}、命名组 (?<name>...);matches() 默认全串锚定

注意:本工具用的是浏览器 ECMAScript 引擎,所以"占有量词 *+"在这里会直接报语法错误。如果你的正则来自 Java/PCRE 且含 *+,需要改成等价写法或去掉占有修饰。国内前端与 Node.js 服务大多走这一套,和服务端 PCRE/Java 的差异最容易在联调时踩坑。

回溯与 DFA:一个坏正则会拖垮服务(含实测)

回溯型引擎的工作方式是"猜":量词 + 先尽量多吃,发现后面接不上就"吐"回来重新分。多数情况很快,但某些嵌套量词组合(如 (a+)+$)会让"吐"的次数随输入长度指数级爆炸,这就是 ReDoS(正则表达式拒绝服务)。在 Web 场景里,攻击者只要发一段精心构造的长文本,就能让校验接口 CPU 占满。国内安全实践(如 CNCERT 风险提示)与 OWASP Top 10:2021(A06)均将 ReDoS 列为典型 Web 风险。下面是用本机实测的耗时(环境:Node v22 / 12 代 Intel i5-12600KF,输入为 n 个 a 后接一个 !,仅供相对比较):

输入长度(n 个 a + 1 个 !)(a+)+$ 耗时 (ms)对照线性 a+ 耗时 (ms)说明
182.22762长度短时两者都很快
208.49734灾难性回溯开始显现
2231.90769约每 +2 长度翻 4 倍
24128.24788已是线性版本的 6 倍多
26517.60818半秒级,用户已明显卡顿
282316.20786超 2 秒,接口基本不可用
3015446.63824约 15 秒,服务被拖垮

对比很清楚:线性正则 a+ 的耗时几乎不随长度变化(约 780ms 是首次编译噪声),而灾难性回溯版本从 18 到 30 个字母,耗时从 2ms 涨到 15 秒,约 7000 倍。经验法则:避免"量词套量词"且内部与外部量词都能匹配同一字符(如 (a+)+、(a*)*、(.*a){10});校验用户输入的前后端都要用简单、确定性的模式,必要时用 RE2 这类线性引擎。本工具会实时高亮匹配,但不会替你拦下 ReDoS 写法,写复杂正则时请自己用加长输入压测一下。

元字符与量词速查表

下面这张表覆盖了 90% 日常写法。重点记住:ECMAScript 里只有贪婪和懒惰,没有占有量词;想要"占有"的防回溯效果,得靠改写结构(如用字符类 [^x]+ 替代 .*)。

符号含义贪婪 / 懒惰 / 占有备注
*匹配前项 0 次或多次贪婪 * / 懒惰 *? / 占有 *+(ECMAScript 不支持)最常见 ReDoS 来源
+匹配前项 1 次或多次贪婪 + / 懒惰 +? / 占有 ++(ECMAScript 不支持)至少一个
?匹配前项 0 次或 1 次贪婪 ? / 懒惰 ?? / 占有 ?+也用于把捕获组变非捕获 (?:...)
{n,m}前项重复 n 到 m 次贪婪 {n,m} / 懒惰 {n,m}? / 占有 {n,m}+{3,} 至少 3 次,{3} 恰 3 次
\b单词边界(零宽)—不匹配字符,只标位置
\d数字—无 u 标志仅 [0-9];加 u 匹配全角数字等 Unicode 数字
\w单词字符—[A-Za-z0-9_];u 下也不含中文
\p{L}Unicode 字母属性—需 u 标志;\p{Script=Han} 可匹配汉字

标志位对照表

标志位决定"怎么搜"而不是"搜什么"。本工具界面提供 g / i / m / s / u 五个开关,下面把每个的作用和 ECMAScript 里的 y(粘性)一并列出:

标志作用本工具默认
g全局:找出所有匹配,而非第一个默认开启
i忽略大小写:a 同时匹配 A默认关闭
m多行:^ $ 匹配每一行的开头与结尾默认关闭
sdotAll:让 . 也能匹配换行符默认关闭
uUnicode 码点模式:支持 \p{...}、正确计量代理对等默认关闭(匹配中文建议开启)
y粘性:只从 lastIndex 位置开始匹配,前端代码中常用界面未直接提供,可在模式里手写

常用模式库:写法与已知局限

下面这些是从项目里捞出来的高频模式,都经过本机编译校验可运行。但正则校验是"形状校验"而非"语义校验",每张都标注了它拦不住什么——这也是 AdSense 类工具页最容易被误用的地方。

用途写法已知局限
邮箱^[a-zA-Z0-9._%+-]+@[a-zA-Z0-9.-]+\.[a-zA-Z]{2,}$不支持引号本地名、IDN 国际域名、连续点;能放过 a@b.c 这种弱地址
URL(http/https)^https?:\/\/[\w.-]+(?:\/[\w./?%&=#-]*)?$不校验端口、不允许 ftp、不防无效 TLD;仅做形状判断
日期 YYYY-MM-DD^\d{4}-\d{2}-\d{2}$拦不住 2026-13-45 这种非法日历值,需另做日期合法性校验
IPv4 地址^(?:(?:25[0-5]|2[0-4]\d|1?\d?\d)\.){3}(?:25[0-5]|2[0-4]\d|1?\d?\d)$允许 192.168.001.1 前导零;不拒绝保留段如 0.0.0.0
中国大陆手机号^1[3-9]\d{9}$只校验 11 位与号段首两位,不验真实在网;虚拟运营商号段亦包含
密码强度^(?=.*[a-z])(?=.*[A-Z])(?=.*\d)(?=.*[^\w\s]).{8,}$只验"大小写+数字+符号+8位",拦不住 Password1! 这类弱口令;要防泄露应配合密码生成器与密码安全指南

三个真实算例(用页面默认输入)

下面三个例子都使用本工具默认测试文本 2026-08-23 发布新版本,2026-09-01 计划更新。(默认只勾选 g 标志),你可以直接复制到工具里复现。想知道文本里有多少个字符、多少个词,可顺手用字数统计工具核对;要对比两版文本的改动,可用文本差异对比。

算例 1:数出所有日期

模式 \d{4}-\d{2}-\d{2},标志 g。在默认文本上得到 2 处匹配:2026-08-23(位置 0)与 2026-09-01(位置 17)。这正是占位提示里给的例子,用来"抓出所有日期"最直接。

算例 2:把年月日拆进捕获组

模式 (\d{4})-(\d{2})-(\d{2}),标志 g。匹配数仍是 2,但每个匹配多出 3 个捕获组:第 1 组年(2026)、第 2 组月(08 / 09)、第 3 组日(23 / 01)。工具结果区的"捕获组"列表会把这些分组列出来,方便你按字段提取。

算例 3:抽出全部数字

模式 \d+,标志 g。默认文本里共有 6 处数字匹配:2026@0、08@5、23@8、2026@17、09@22、01@25。注意 \d+ 把"2026"当一整个数,而不是 4 个单数字——这就是量词"贪婪吃满"的表现。

写正则的常见坑

① 转义。 . * + ? ( ) 在正则里有特殊含义,想匹配本身要加反斜杠(\. \*)。在编程语言字符串里反斜杠本身还要再转义,这是"明明写对了却报错"的头号原因。
② 贪婪 vs 懒惰。 默认量词贪婪(尽量多匹配),要"最少匹配"加 ? 变懒惰(.*?)。提取 HTML 标签时搞错这点,会一次吞掉半篇文章。
③ 忘锚点。 校验"整串是不是邮箱"要用 ^ 和 $ 包住;否则只匹配到中间一段,校验就"漏网"了。

补充问答

为什么同一个正则在这个工具里能跑,放到 Python / Java 里却报错或结果不同?因为底层引擎方言不同(见上方引擎家族表)。最典型的差异:ECMAScript 不支持占有量词 *+,而 Java/PCRE 支持;PCRE 的 (*SKIP) 回溯控制在 JS 里没有;Python re 默认 . 不匹配换行,和 JS 开 s 后不同。跨语言搬运正则前,先对照表确认语法是否互通。

\d 在加 u 和不加 u 时,匹配范围为什么不一样?不加 u 时 \d 只等价于 [0-9](ASCII 数字);加上 u 标志后,ECMAScript 按 Unicode 处理,\d 会匹配阿拉伯文、全角等世界各语种数字字符。如果你的文本可能含全角数字"2026",校验时必须开 u,否则会漏判。

怎样写出既校验邮箱又不卡死的正则?邮箱正则本身一般不构成 ReDoS,风险多在"嵌套量词 + 长文本"场景。原则:邮箱/URL 校验用上方模式库里的扁平写法,不要写成 ([\w]+@[\w]+)+ 这类量词套量词;对来自用户的超长输入,先长度截断再校验,或用 RE2 等线性引擎。更多防爆破与弱口令的思路见密码安全指南。

捕获组太多会影响性能吗?命名组 (?<name>...) 和匿名组有区别吗?功能上等价,命名组只是让结果更易读、后续代码不易因"加了一个组导致序号错位"而出错。性能上两者开销都很小;真正要吃性能的是"嵌套捕获组 + 灾难性回溯"的组合,和组本身叫不叫名字无关。

为什么我对超大文本用复杂正则时浏览器会卡?大概率是触发了 ReDoS(见上方实测表)。浏览器也是回溯型引擎,长文本下 (a+)+ 这类写法会指数爆炸。排查办法:用加长输入在工具里压测,看匹配耗时是否随长度陡增;若是,改用字符类边界(如 [^x]+)消除嵌套量词,或拆分多次简单匹配。