簡體   English   中英

在 C# 中提取 substring 的最快方法

[英]Fastest way to extract a substring in C#

我將處理數千個字符串(平均大小約為 150kB)。 它們中的每一個都包含零個或多個以下形式的子字符串:

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

我想提取所有此類鏈接並將它們放入列表中。

此外,還有另一個固定字符串,之后我要查找的字符串將不會出現。

獲取琴弦的最快方法是什么?

假設字符串是正確格式的 HTML 格式,您可以使用XmlReader class 輕松解析它們,它是非緩存和僅轉發(這使得它非常非常快)。 您只需在檢索其“href”屬性值時尋找合適的節點。

您也可以使用像.SubString()這樣的普通字符串操作,但是您必須編寫許多子例程來處理異常情況。 這里的重點是避免使用 RegEx,因為它將是最慢的。

SubString() 選項

正如 Teoman Soygul 所指出的,有一個 SubString() 選項,我不知道它是慢還是快,因為我沒有並排測試它們。

現在,這沒有被正確地分解為子方法,但應該給你一個大致的想法。
我只是使用ReadOnlyCollection因為它是我在不需要進一步操作列表時所習慣的。 將其更改為您喜歡的任何 output 列表類型。

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

這會產生一個包含鏈接的集合:
在此處輸入圖像描述

一些手動解析可能是解決這個問題的最快方法。 正則表達式也是可能的,因為它實際上只是解析鏈接的一個非常簡單的案例,而不是整個 HTML 文檔,但這很容易在性能方面扼殺那些大文件。

現在,讓我先說明一下,我根本沒有測試過它,而且我覺得發布它有點臟(我確信它需要更多的邊緣情況檢查以避免錯誤),但是這里是 go:

    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 敏捷包可能是您的最佳選擇,或者如果無法幫助,則可能是正確完成的正則表達式。 我發布了此手動解析,因此您可以使用另一種替代方法進行性能測試。

通常,Regex 處理小文件時速度更快。 如果文件大小變大(根據我的經驗>~60Kb),那么正則表達式會變慢(甚至 static,編譯等)。 找到用非常好的英語描述的確切情況:

以高效的方式和總線因素剝離空 XmlElements

發現什么是“高總線因素”,玩得開心。 它給我帶來了一天的好心情。

我認為在這種情況下,如果字符串平均足夠大並且包含零個或多個子字符串,最好的方法是像這樣使用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
    }

}

從基准測試來看,生成 substring 的最佳方法是使用 ReadOnlySpans,而不是使用 string.Split

string.Split 要慢得多,並且會向 memory 寫入很多內容,而 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

暫無
暫無

聲明:本站的技術帖子網頁,遵循CC BY-SA 4.0協議,如果您需要轉載,請注明本站網址或者原文地址。任何問題請咨詢:yoyou2525@163.com.

 
粵ICP備18138465號  © 2020-2024 STACKOOM.COM