搜索时间时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首先搜索最不频繁出现的":",并且只有在找到匹配项后,才检查之前的两个字符

或者也许还有更好的方法?

搜索时间时Regex性能不好(xx:xx:xx)

您可以在问题中同时使用这两个正则表达式。首先匹配前导冒号表达式,以快速查找或排除可能的行。如果成功,则使用完全替换表达式。

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;
}

在第二个和第三个想法中,finderchanger应该在读取任何行之前声明并给定值。不需要在行读取循环内部执行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倍加速:-)

谢谢大家!