我在c#中使用WinForms . NET 2.0。
我有文本文件,大约1000-1500行。它们中的某些行以4个或更多字母的单词开头,我必须在这些单词后面加一个冒号。这些行开头是否有空格是可选的,除了这些单词之外,该行可以包含更多文本。下面是一个例子:
lda $00,x
mov $20
rep #$20
tax
lda #$0000,y
word
... ; comment
anotherword ; this word has whitespace before it.
同样,如果已经有冒号,则忽略它们以防止添加更多冒号。下面是我的代码:
Regex R = new Regex(@"^s*(?<word>[A-Za-z0-9_]{4,})", RegexOptions.Multiline); //keep the words stored in a group called word
MatchCollection M = R.Matches(txt); //let my text file string be "txt"
foreach (Match m in M)
{
string mm = m.Groups["word"].Value;
if (!Regex.IsMatch(txt, @"^s*b" + mm + @"b:", RegexOptions.Multiline)) // if already a colon, return
txt = Regex.Replace(txt, @"^s*b" + mm + @"b", mm + ":", RegexOptions.Multiline);
}
它工作和所有,但问题是?它太慢了。我在文本文件中做了其他操作,但我已经确认它们很快,问题在于上面正则表达式中的两个"s*"。当我同时删除它们时,搜索速度会提高10倍。
我该如何解决这个问题?
@TimPietzcker的替代方案:
result = Regex.Replace(subject, @"^(?>(s*w{4,}))(?!:)", "$1:", RegexOptions.Multiline);
,其中(?>...)
是一个原子分组。当regex引擎进入原子分组时,不允许回溯该分组所使用的输入中的任何地方。
现在,为什么这是有益的?考虑这样一行:
ab3 #13 spaces, then a, b, 3
如果不使用原子分组,当regex无法匹配第二个量词中的第4个字符时,它必须回溯到a
之前的字符:但它是一个空格,它不匹配。以此类推,直到它到达行开始前的字符,^
不匹配,然后才声明失败(s*
可以匹配空字符串)。
使用原子分组,引擎将不会以这种方式回溯,这是一个巨大的性能增益,特别是当您处理大数据时。
我看到了三个主要问题:
-
基本上在每行上执行相同的正则表达式匹配多达三次。正如Tim所演示的,无论该行是否匹配正则表达式,您都不需要多次触摸该行。此外,在使用相同的正则表达式执行Replace()之前,您不需要使用Match()或IsMatch()测试字符串。如果字符串不匹配正则表达式,Replace()将简单地返回原封不动的字符串。
-
没有必要像你现在做的那样在我的手上做一个替换的弦。
-
s
匹配所有空白字符,包括换行符。如果(例如)有9个空行后面跟着一个匹配行,则正则表达式将消耗全部10行。如果第十行*不匹配,regex引擎将放弃匹配尝试,并从第二个空白行开始重新尝试。第三行,第四行,等等。如果从正则表达式中删除s*
有很大的影响,这可能是原因:它试图匹配大量不必要的空白。如果你知道你正在寻找的字符串将总是在单行上,你应该确保正则表达式只匹配水平空白。
来演示:
result = Regex.Replace(subject, @"(?m)^([ t]*w{4,})(?![w:])", "$1:");
解释:
-
(?m)
仅仅是一种更方便的方式来指定Multiline选项。 -
^([ t]*w{4,})
匹配一行中的第一个单词以及任何前导空格,并将其全部捕获在组#1中。 -
(?![w:])
为负正向;它断言下一个字符(如果有的话)既不是单词字符也不是冒号。这确保你已经阅读了整个单词,并且单词后面没有冒号。 - 在replacement参数中,
$1
是第一个捕获组内容的占位符。
我注意到您的regex匹配前导空白而不捕获它,并且您没有在替换中添加任何空白。其效果是从执行此替换的任何行中删除前导空格,但不会从任何其他行中删除。如果你真的想这样做,你可以把^([ t]*w{4,})
改成^[ t]*(w{4,})
。