为什么正则表达式不关心字符串长度

Why regex does not care about string length

为什么正则表达式的输入长度不影响性能,这怎么可能?

生成的字符串是这样的:128个随机字符。然后是括号内的两个数字。这重复了很多次。

128 radnom characters....(-2435346|45436) 128 radnom characters....(-32525562|-325346)

正则表达式获取括号内的所有数字。这是模式。

\(([-+]?\d+\|[-+]?\d+)\)

所以比赛会像

-2435346|45436
-32525562|-325346
etc...

这是执行基准测试的代码。我在生成输入后启动秒表,因为我只想评估匹配时间。

Random rand = new Random();
Func<string> getRandString = // generates 128 random characters.
    () => Enumerable.Range(0, 128).Select(x => (char) rand.Next()).Aggregate("", (a, b) => a + b);
Func<int> getRandInteger = () => rand.Next(); // generates random number.
string format = "{0}({1}|{2})";

// Generate the big string.
StringBuilder bigstr = new StringBuilder();
for (int i = 0; i < 100; i++) // repeat 100 times.
{
    bigstr.Append(string.Format(format, getRandString(), getRandInteger(), getRandInteger()));
}
string input = bigstr.ToString();

Stopwatch stopwatch = Stopwatch.StartNew();
var matches = Regex.Matches(input, @"\(([-+]?\d+\|[-+]?\d+)\)");
stopwatch.Stop();
Console.WriteLine("Time Elapsed :\t{0}\nInputLength :\t{1}\nMatches Count :\t{2}", stopwatch.Elapsed, input.Length, matches.Count);

如果我重复循环 10 次,这是我的控制台中的输出。

Time Elapsed :   00:00:00.0004132
InputLength :    1500
Matches Count :  10

如果我重复循环 1000 次。

Time Elapsed :   00:00:00.0004373 // seriously?
InputLength :    149937
Matches Count :  1000

如果我重复循环 1000000 次。

Time Elapsed :   00:00:00.0004900 // wtf?
InputLength :    149964452
Matches Count :  1000000

不信截图

这是某种懒惰的评价吗?如果是这样那么它如何显示匹配数?我是如何在调试器下做到这一点的,我可以看到匹配项。

我的正则表达式模式中有什么特别的东西可以让它变快吗?但是字符串长度如何不影响性能呢?看不懂。

发生这种情况的原因之一是 matches.Count 属性 被 延迟计算 。当您停止秒表时,匹配器准备进行匹配,但它没有找到任何东西。只有当你调用 matches.Count 时它才开始工作,但你此时已经停止计时了。

一个简单的解决方案是将 matches.Count 调用移动到您计时的代码部分。

另一个原因是您将解析正则表达式所需的时间包括在内。这是一个相对昂贵的操作,所以你应该在打开秒表之前进行更好的计时:

Regex testRegex = new Regex(@"\(([-+]?\d+\|[-+]?\d+)\)");
Stopwatch stopwatch = Stopwatch.StartNew();
var matches = testRegex.Matches(input);
var matchesCount = matches.Count;
stopwatch.Stop();
Console.WriteLine("Time Elapsed :\t{0}\nInputLength :\t{1}\nMatches Count :\t{2}", stopwatch.Elapsed, input.Length, matchesCount);

现在 10 场比赛与 10,000 场比赛的时间增长变得相当可观 (00:00:00.0129844 vs. 00:00:00.0843982),尽管输入长度相差千倍时没有预期的那么大。