簡體   English   中英

如何在C#中的字符串FAST中獲取char出現的次數?

[英]How to get the # of occurances of a char in a string FAST in C#?

我有一個txt文件。 現在,我需要逐行加載它,並檢查整個文件中“ @”的次數。

所以,基本上,我只有一行字符串,如何快速獲取“ @”的出現次數?

我需要快速計數,因為我們有很多這樣的文件,每個文件大約300-400MB。

我搜索了一下,看來直接的方法是最快的方法:

int num = 0;
foreach (char c in line)
{
    if (c == '@') num++;
}

是否有其他方法可以比這更快? 還有其他建議嗎?

  • 如果需要,我們不必逐行加載txt文件,但是我們確實需要知道每個文件中的#行。

謝謝

最快的方法實際上與I / O功能和計算速度有關。 通常,了解什么是最快的技術的最佳方法是對它們進行基准測試。

免責聲明 :(當然)結果綁定到我的機器上,並且在不同的硬件上可能會有很大差異。 為了進行測試,我使用了一個大約400MB的文本文件。 如果感興趣,可以在此處下載文件(壓縮)。 可執行文件編譯為x86。

選項1:讀取整個文件,不進行並行化

long count = 0;

var text = File.ReadAllText("C:\\tmp\\test.txt");
for(var i = 0; i < text.Length; i++)
if (text[i] == '@')
    count++;

結果:

  • 平均執行時間: 5828 ms
  • 平均進程內存: 1674 MB

這是“幼稚”的方法,該方法讀取內存中的整個文件,然后使用for循環(比foreach或LINQ快得多)。

由於預期的進程占用的內存非常高(大約是文件大小的4倍),這可能是由於內存中的字符串大小( 此處有更多信息)和字符串處理開銷的組合所致。

選項2:分塊讀取文件,不進行並行化

long count = 0;
using(var file = File.OpenRead("C:\\tmp\\test.txt"))
using(var reader = new StreamReader(file))
{
    const int size = 500000; // chunk size 500k chars
    char[] buffer = new char[size];

    while(!reader.EndOfStream)
    {
        var read = await reader.ReadBlockAsync(buffer, 0, size); // read chunk

        for(var i = 0; i < read; i++)
        if(buffer[i] == '@')
            count++;
    }
}

結果:

  • 平均執行時間: 4819 ms
  • 平均進程內存: 7.48 MB

這是出乎意料的。 在此版本中,我們以50萬個字符的塊讀取文件,而不是將其完全加載到內存中,並且執行時間甚至比以前的方法還要短。 請注意,減小塊大小會增加執行時間(由於開銷)。 內存消耗極低(正如預期的那樣,我們僅將大約500kB / 1MB的內存直接加載到char數組中)。

可以通過更改塊大小來獲得更好(或更差)的性能。

選項3: 並行方式分塊讀取文件

long count = 0;
using(var file = File.OpenRead("C:\\tmp\\test.txt"))
using(var reader = new StreamReader(file))
{
    const int size = 2000000; // this is roughly 4 times the single threaded value
    const int parallelization = 4; // this will split chunks in sub-chunks processed in parallel
    char[] buffer = new char[size];

    while(!reader.EndOfStream)
    {
        var read = await reader.ReadBlockAsync(buffer, 0, size);

        var sliceSize = read/parallelization;
        var counts = new long[parallelization];

        Parallel.For(0, parallelization, i => {
            var start = i * sliceSize;
            var end = start + sliceSize;

            if(i == parallelization)
                end += read % parallelization;

            long localCount = 0;
            for(var j = start; j < end; j++)
            {
                if(buffer[(int)j] == '@')
                    localCount++;
            }
            counts[i] = localCount;
        });

        count += counts.Sum();
    }
}

結果:

  • 平均執行時間: 3363 ms
  • 平均進程內存: 10.37 MB

正如預期的那樣,該版本在單線程上的性能更好,但沒有我們想象的好4倍。 與第一個版本相比,內存消耗仍然非常低(與以前相同的考慮),並且我們正在利用多核環境。

諸如塊大小和並行任務數之類的參數可能會顯着改變結果,您應該反復嘗試才能找到最適合您的組合。

結論

我傾向於認為“將所有內容加載到內存中”是最快的版本,但這實際上取決於字符串處理的開銷和I / O速度。 並行塊方法在我的機器中似乎是最快的,這應該使您有一個主意:如有疑問,只需對其進行基准測試即可。

您可以測試它是否更快,但是更短的編寫方法是:

int num = File.ReadAllText(filePath).Count(i => i == '@');

嗯,但是我剛剛看到您也需要行數,所以這很相似。 再次,需要與您擁有的進行比較:

var fileLines = File.ReadAllLines(filePath);
var count = fileLines.Length();
var num = fileLines.Sum(line => line.Count(i => i == '@'));

您可以使用指針。 我不知道這是否會更快。 您將必須進行一些測試:

static void Main(string[] args)
{
    string str = "This is @ my st@ing";
    int numberOfCharacters = 0;

    unsafe
    {
        fixed (char *p = str)
        {
            char *ptr = p;
            while (*ptr != '\0')
            {
                if (*ptr == '@')
                    numberOfCharacters++;
                ptr++;
            }
        }
    }

    Console.WriteLine(numberOfCharacters);
}

請注意,您必須進入項目屬性並允許使用不安全的代碼,此代碼才能正常工作。

暫無
暫無

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

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