我在C#中遇到了Regex性能问题。
我需要更换一个非常大的字符串(270k字符串,不要问为什么......)。正则表达式匹配约3k次。
private static Regex emptyCSSRulesetRegex = new Regex(@"[^\};\{]+\{\s*\}", RegexOptions.Compiled | RegexOptions.Singleline);
public string ReplaceEmptyCSSRulesets(string css) {
return emptyCSSRulesetRegex.Replace(css, string.Empty);
}
我传递给方法的字符串如下所示:
.selector-with-statements{border:none;}.selector-without-statements{}.etc{}
目前替换过程在C#中占用 1500ms ,但是当我在Javascript中完全相同时,只需要 100ms 。
我用于计时的Javascript代码:
console.time('reg replace');
myLargeString.replace(/[^\};\{]+\{\s*\}/g,'');
console.timeEnd('reg replace');
我还尝试通过以相反的顺序循环匹配来替换,并替换StringBuilder中的字符串。那没有帮助。
我对C#和Javascript在这种情况下的性能差异感到惊讶,我认为我做错了什么但我想不出任何事情。
答案 0 :(得分:2)
我无法真正解释Javascript和C#(*)之间的时差。但是你可以尝试提高模式的性能(产生大量的回溯):
private static Regex emptyCSSRulesetRegex = new Regex(@"(?<keep>[^};{]+)(?:{\s*}(?<keep>))?", RegexOptions.Compiled);
public string ReplaceEmptyCSSRulesets(string css) {
return emptyCSSRulesetRegex.Replace(css, @"${keep}");
}
原始模式的一个问题是,当大括号不为空(或者没有用空格填充)时,正则表达式引擎将继续测试开始大括号之前的每个位置(总是具有相同的结果)。示例:使用字符串abcd{1234}
,您的模式将从a
开始,然后b
...
我建议的模式将消耗abcd
,即使后面没有空的大括号,因此bcd
的位置不会被测试。
abcd
在名为keep
的组中捕获,但是当找到空的大括号时,捕获组将被空捕获组覆盖。
您可以了解两种模式所需的步骤数(检查调试器):
注意:如果将[^}{;]+
括在原子组中,则可以改进原始模式。此更改将所需的步数除以2(与原始步骤相比),但即使如此,由于前面解释的原因,步数仍然很高。
(*)javascript正则表达式引擎可能足够智能,无法重试所有这些位置,但这只是一个假设。