为什么正则表达式中 \\d 比 [0-9] 慢?C# 解析

面向初学者的在线Python训练器

轻松学习Python,无需理论负担。在浏览器中直接解决带自动检查的实践任务,无需安装任何东西。

开始课程

引言:关于正则表达式速度的误区

\n\n

如果你曾经用 C# 编写过代码并使用过正则表达式,你很可能遇到过这样的两难选择:使用 \\d 还是 [0-9] 来查找数字?乍一看没有区别——两者都能找到数字。但那些追求极致性能的经验丰富的开发者知道:\\d 可能比 [0-9] 慢得多。

\n\n

在 Stack Overflow 上,这个问题获得了数千次浏览和数十条评论。今天,我们不仅仅是复述答案,而是深入 .NET Regex 引擎的底层,弄清楚为什么会这样以及它如何影响你的代码。我们将用 C# 编写真实的基准测试,查看 IL 代码,并了解何时应该警惕,何时可以放松。

\n\n

准备好了吗?开始吧!

\n\n

C# 中的 \\d 和 [0-9] 是什么?

\n\n

首先——基础知识。在大多数正则表达式实现中(包括 C#),\\d 是“任何数字”的快捷方式。而 [0-9] 是一个字符类,显式列举了从 '0' 到 '9' 的范围。

\n\n

看起来是一样的。但魔鬼,总是在细节中。

\n\n

Unicode vs ASCII

\n\n

在 .NET 中(从 .NET Framework 4.5 开始),\\d 默认匹配Unicode 类别 Nd(数字,十进制数字)中的任何字符。这不仅包括阿拉伯数字 0-9,还包括:

\n
    \n
  • 天城文数字(०१२३...)
  • \n
  • 泰米尔文数字
  • \n
  • 东阿拉伯数字
  • \n
  • 以及其他数十种数字系统
  • \n
\n\n

而 [0-9] 是硬编码的 ASCII 码范围(48-57)。只有 0,1,2,3,4,5,6,7,8,9。没有 Unicode。

\n\n

为什么速度不同?底层解析

\n\n

现在关键问题:为什么 \\d 更慢?让我们从引擎层面来理解。

\n\n

1. 检查次数

\n\n

当引擎遇到 [0-9] 时,它做一件事:检查字符代码是否在 48-57 范围内。这是一次比较(如果考虑边界,则是两次)。

\n\n

当引擎遇到 \\d 时,它需要:

\n
    \n
  1. 获取字符的 Unicode 类别(这已经比简单的 int 操作更昂贵)。
  2. \n
  3. 将其与 Nd 类别进行比较。
  4. \n
\n\n

Unicode 类别不仅仅是一个数字,而是一个通过内部表计算的完整标志。对于拉丁数字,结果相同,但到达结果的路径更长。

\n\n

2. 缓存与 JIT

\n\n

在 .NET 中,Regex 被编译为中间代码(IL)甚至本机代码(如果使用 RegexOptions.Compiled)。对于 [0-9],JIT 可以生成非常简单的指令序列:几次比较和条件跳转。对于 \\d,则需要调用 CharUnicodeInfo.GetUnicodeCategory 方法并进行后续比较。

\n\n

方法调用有开销:参数压栈、调用、栈检查。即使方法很小,它仍然比内联比较慢。

\n\n

3. 真实基准测试示例

\n\n

让我们用 C# 编写一个简单的测试,使用 BenchmarkDotNet(如果你要重复测试,请务必通过 NuGet 安装它):

\n\n
using BenchmarkDotNet.Attributes;\nusing BenchmarkDotNet.Running;\nusing System.Text.RegularExpressions;\n\npublic class RegexBenchmark\n{\n    private static readonly Regex _digitPattern = new Regex(@"\\d", RegexOptions.Compiled);\n    private static readonly Regex _rangePattern = new Regex(@"[0-9]", RegexOptions.Compiled);\n    \n    private const string TestString = "Hello123World456!";\n\n    [Benchmark]\n    public bool DigitPattern() => _digitPattern.IsMatch(TestString);\n\n    [Benchmark]\n    public bool RangePattern() => _rangePattern.IsMatch(TestString);\n}\n\nclass Program\n{\n    static void Main() => BenchmarkRunner.Run<RegexBenchmark>();\n}
\n\n

结果(在 .NET

博客

书籍推荐