在C#中提取子字符串的最快方法

时间:2011-06-02 12:17:44

标签: c# regex string

我将处理数千个字符串(平均大小约为150kB)。它们中的每一个都包含以下形式的零个或多个子串:

<a href="/link/i/want">Fixed_String</a>

我想提取所有这些链接并将它们放入列表中。

此外,还有另一个固定字符串,之后我找不到的字符串将会出现。

获得琴弦的最快方法是什么?

6 个答案:

答案 0 :(得分:3)

假设字符串具有格式正确的HTML格式,您可以使用XmlReader类轻松地解析它们,该类只是非缓存和转发(这使得它非常快)。您只需寻找正确的节点来检索其'href'属性的值。

您也可以使用.SubString()之类的常规字符串操作,但是您必须编写许多子例程来处理异常情况。这里的要点是避免RegEx,因为它将是最慢的一群。

答案 1 :(得分:3)

  

SubString()选项

如Teoman Soygul所述,有一个SubString()选项,我不知道它是否更慢或更快,因为我没有并排测试它们。

现在,这并没有适当地分解为子方法,但应该给你一般的想法 我只使用ReadOnlyCollection因为当我不需要进一步操作列表时,它就是我习惯的。将其更改为您喜欢的输出列表类型。

someText变量最有可能最终导致GetLinks偏离。

public ReadOnlyCollection<string> GetLinks()
{
    string startingText = "href=''";
    string endText = "''>";
    string stopText = "Fixed_String";
    string someText = @"what is this text <a href=''/link/i/want''>somenormallink</a> some random text <a href=''/another link/i/want''>Fixed_String</a> some more radnom txt ";

    List<string> myLinks = new List<string>();

    string[] rawLinks = someText.Split(new string[] { "<a " }, StringSplitOptions.None);

    foreach (string rawLink in rawLinks)
    {
        if (!rawLink.StartsWith(startingText))
        {
            continue;
        }

        myLinks.Add(rawLink.Substring(startingText.Length, rawLink.IndexOf(endText, 1) - startingText.Length));


        if (rawLink.Contains(stopText))
        {
            break;
        }
    }


    return new ReadOnlyCollection<string>(myLinks);
}

包含链接的集合中的结果:
enter image description here

答案 2 :(得分:2)

一些手动解析可能是解决此问题的最快方法。正则表达式也是可能的,因为它实际上只是解析链接而不是整个HTML文档的一个非常简单的例子,但这可能很容易扼杀那些大文件,性能明智。

现在,让我先说明一下,我根本没有对此进行测试,我觉得有点脏(我确定它需要更多的边缘检查以避免错误),但是你去了:

    const char[] quotes = new char[] { '"', '\'' };

    private List<string> ExtractLinks(string html)
    {
        var links = new List<string>();
        string searchFor = ">Fixed_String</a>";

        for (int i = html.IndexOf(searchFor); i >= 0; i = html.IndexOf(searchFor, i + searchFor.Length))
        {
            string href = ExtractHref(html, i);
            if (!String.IsNullOrEmpty(href))
                links.Add(href);
        }

        return links;
    }

    private string ExtractHref(string html, int backtrackFrom)
    {
        int hrefStart = -1;

        // Find "<a", but limit search so we don't backtrack forever
        for (int i = backtrackFrom; i > backtrackFrom - 255; i--)
        {
            if (i < 0)
                return null;

            if (html[i] == '<' && html[i + 1] == 'a')
            {
                hrefStart = html.IndexOf("href=", i);
                break;
            }
        }

        if (hrefStart < 0)
            return null;

        int start = html.IndexOfAny(quotes, hrefStart);
        if (start < 0)
            return null;

        int end = html.IndexOfAny(quotes, start + 1);
        if (end < 0)
            return null;

        return html.Substring(start + 1, end - start - 1);
    }

XmlReader可能不合适,因为您很可能无法保证这些文件是XHTML格式的。如果你想做正确的解析,HTML Agility Pack可能是你的最佳选择,或者如果无法帮助你可能是正确完成的正则表达式。我发布了这个手册解析,因此您可以使用另一种替代方法进行性能测试。

答案 3 :(得分:1)

一般来说,使用小文件时Regex会更快。如果文件大小变大(根据我的经验,大约60Kb),那么正则表达式变得更慢(甚至是静态的,编译等)。找到非常好的英语描述的确切情况:

Stripping Out Empty XmlElements in a Performant Way and the Bus Factor

有趣的是发现什么是“ High Bus Factor ”。这给我带来了一天的好心情。

答案 4 :(得分:0)

我认为在这种情况下,有些字符串平均足够大并且包含零个或多个子字符串,最好的方法是使用Regex class,如下所示:

string anchorPattern = @"<(.|/)a(.|\n)+?>";

foreach (string str in strings)
{
    Regex regex = new Regex(anchorPattern);

    foreach (Match match in regex.Matches(str))
    {
         // do here what you want with substring in match.Value
    }

}

答案 5 :(得分:0)

根据基准测试,生成子字符串的最佳方法是使用ReadOnlySpans,而不是使用string.Split

string.Split速度慢得多,并且会向内存中写入大量内容,而Readonly仅将写入内容范围写入

|                  Method |      Mean |    Error |    StdDev |    Median |  Gen 0 | Gen 1 | Gen 2 | Allocated |
|------------------------ |----------:|---------:|----------:|----------:|-------:|------:|------:|----------:|
|    SpanParseLongVersion |  17.84 ns | 0.385 ns |  0.674 ns |         - |      - |     - |     - |         - |
| ParseLongFWVersionSplit |  95.74 ns | 1.928 ns |  3.274 ns |  95.05 ns | 0.0373 |     - |     - |     176 B |
public const string FWLongVersion= "FWabcdefghijklmnopqrstuvwxyz-1.0000000000001";

[Benchmark]
public void SpanParseLongVersion()
{
    var dashChar = '-';
    var vSpan = FWLongVersion.AsSpan();
    var length = FWLongVersion.Length;

    ReadOnlySpan<char> fwVersion = null;

    for (int x = 0; x < length; x++)
    {
        if (vSpan[x] == dashChar)
        {
            fwVersion = vSpan.Slice(x + 1, 5);
            break;
        }
    }
}

[Benchmark]
public void ParseLongFWVersionSplit()
{
    var x = FWLongVersion.Split('-');
}

另外,使用string.StartsWithstring.Contains也不是很好....如何有效地检查这一点,请在这里查看我的帖子: https://stackoverflow.com/a/64395744/4926590