搜索时间时Regex性能不好(xx:xx:xx)
本文关键字:xx 时间 Regex 性能 搜索 | 更新日期: 2023-09-27 18:00:31
我必须处理一个大文件(几个MB),并从中删除用时间标记的注释。一个例子:
blablabla 12:10:40 I want to remove this
blablabla some more
even more bla
过滤后,我希望它看起来像这样:
blablabla
blablabla some more
even more bla
最好的方法应该是放松Regex:
Dataout = Regex.Replace(Datain, "[012][0123456789]:[012345][0123456789]:[012345][0123456789].*", string.Empty, RegexOptions.Compiled);
现在这完全符合我的目的,但有点慢。。我认为这是因为前两个字符[012]和[0123456789]与很多数据匹配(这是一个包含十六进制数据的ASCII文件,比如"0045ab0123"等)。所以Regex经常在前两个字上匹配。
当我将Regex更改为时
Dataout = Regex.Replace(Datain, ":[012345][0123456789]:[012345][0123456789].*", string.Empty, RegexOptions.Compiled);
这是一个巨大的加速,可能是因为文件中根本没有太多":"。好的但我仍然需要检查第一个":"之前的两个字符是否为数字,然后删除行的其余部分。
所以我的问题归结为:
- 我如何让Regex首先搜索最不频繁出现的":",并且只有在找到匹配项后,才检查之前的两个字符
或者也许还有更好的方法?
您可以在问题中同时使用这两个正则表达式。首先匹配前导冒号表达式,以快速查找或排除可能的行。如果成功,则使用完全替换表达式。
MatchCollection mc = Regex.Matches(Datain, ":[012345][0123456789]:[012345][0123456789].*"));
if ( mc != null && mc.Length > 0 )
{
Dataout = Regex.Replace(Datain, "[012][0123456789]:[012345][0123456789]:[012345][0123456789].*", string.Empty, RegexOptions.Compiled);
}
else
{
Dataout = Datain;
}
一种变体可能是
Regex finder = new Regex(":[012345][0123456789]:[012345][0123456789].*");
Regex changer = new regex("[012][0123456789]:[012345][0123456789]:[012345][0123456789].*");
if ( finder.Match(Datain).Success)
{
Dataout = changer.Replace(Datain, string.Empty);
}
else
{
Dataout = Datain;
}
另一种变体是使用如上所述的CCD_ 1。如果找到了字符串,那么只需检查前两个字符是否为数字。
Regex finder = new Regex(":[012345][0123456789]:[012345][0123456789].*");
Match m = finder.Match(Datain);
if ( m.Success && m.Index > 1)
{
if ( char.IsDigit(DataIn[m.index-1]) && char.IsDigit(DataIn[m.index-2])
{
Dataout = m.Index-2 == 0 ? string.Empty : DataIn.Substring(0, m.Index-2);
}
else
{
Dataout = Datain;
}
}
else
{
Dataout = Datain;
}
在第二个和第三个想法中,finder
和changer
应该在读取任何行之前声明并给定值。不需要在行读取循环内部执行new Regex(...)
。
您可以使用DateTime.TryParseExact
检查一个单词是否为时间,并获取之前的所有单词。这里有一个LINQ查询来清除路径中的所有行,也许它更有效:
string format = "HH:mm:ss";
DateTime time;
var cleanedLines = File.ReadLines(path)
.Select(l => string.Join(" ", l.Split().TakeWhile(w => w.Length != format.Length
|| !DateTime.TryParseExact(w, format, CultureInfo.InvariantCulture, DateTimeStyles.None, out time))));
如果性能非常关键,您还可以创建一个专门的方法来优化此任务。这里有一种方法应该更有效:
public static string SubstringBeforeTime(string input, string timeFormat = "HH:mm:ss")
{
if (string.IsNullOrWhiteSpace(input))
return input;
DateTime time;
if (input.Length == timeFormat.Length && DateTime.TryParseExact(input, timeFormat, CultureInfo.InvariantCulture, DateTimeStyles.None, out time))
{
return ""; // full text is time
}
char[] wordSeparator = {' ', ''t'};
int lastIndex = 0;
int spaceIndex = input.IndexOfAny(wordSeparator);
if(spaceIndex == -1)
return input;
char[] chars = input.ToCharArray();
while(spaceIndex >= 0)
{
int nonSpaceIndex = Array.FindIndex<char>(chars, spaceIndex + 1, x => !wordSeparator.Contains(x));
if(nonSpaceIndex == -1)
return input;
string nextWord = input.Substring(lastIndex, spaceIndex - lastIndex);
if( nextWord.Length == timeFormat.Length
&& DateTime.TryParseExact(nextWord, timeFormat, CultureInfo.InvariantCulture, DateTimeStyles.None, out time))
{
return input.Substring(0, lastIndex);
}
lastIndex = nonSpaceIndex;
spaceIndex = input.IndexOfAny(wordSeparator, nonSpaceIndex + 1);
}
return input;
}
样本数据和测试:
string[] lines = { "blablabla 12:10:40 I want to remove this", "blablabla some more", "even more bla ", "14:22:11" };
foreach(string line in lines)
{
string newLine = SubstringBeforeTime(line, "HH:mm:ss");
Console.WriteLine(string.IsNullOrEmpty(newLine) ? "<empty>" : newLine);
}
输出:
blablabla
blablabla some more
even more bla
<empty>
最后我选择了这个:
bool MeerCCOl = true;
int startpositie = 0;
int CCOLfound = 0; // aantal keer dat terminal output is gevonden
while(MeerCCOl)
{
Regex rgx = new Regex(":[0-5][0-9]:[0-5][0-9]", RegexOptions.Multiline | RegexOptions.Compiled);
Match GevondenColon = rgx.Match(VlogDataGefilterd,startpositie);
MeerCCOl = GevondenColon.Success; // CCOL terminal data gevonden, er is misschien nog meer..
if (MeerCCOl && GevondenColon.Index >= 2)
{
CCOLfound++;
int GevondenUur = 10 * (VlogDataGefilterd[GevondenColon.Index - 2] - '0') +
VlogDataGefilterd[GevondenColon.Index - 1] - '0';
if (VlogDataGefilterd[GevondenColon.Index - 2] >= '0' && VlogDataGefilterd[GevondenColon.Index - 2] <= '2' &&
VlogDataGefilterd[GevondenColon.Index - 1] >= '0' && VlogDataGefilterd[GevondenColon.Index - 1] <= '9' &&
GevondenUur>=0 && GevondenUur<=23)
{
Regex rgx2 = new Regex("[012][0-9]:[0-5][0-9]:[0-5][0-9].*", RegexOptions.Multiline);
VlogDataGefilterd = rgx2.Replace(VlogDataGefilterd, string.Empty, 1, (GevondenColon.Index - 2));
startpositie = GevondenColon.Index - 2; // start volgende match vanaf de plek waar we de
}
}
}
它首先搜索与:xx:xx匹配的字符,然后检查前面的2个字符。如果它被识别为一个时间,它就会删除整个东西。额外的好处是,通过单独检查小时数,我可以确保小时数为00-23,而不是00-29。此外,匹配的数量也是以这种方式计算的。
最初的简单正则表达式耗时约550毫秒。对于相同的数据文件,这段代码(虽然更混乱)大约需要12毫秒。这是惊人的40倍加速:-)
谢谢大家!