web-dev-qa-db-ja.com

StreamReader.Readline()は本当にファイル内の行を数えるための最速のメソッドですか?

しばらく見回していると、ファイルの行数を把握する方法についてかなりの数の議論が見つかりました。

たとえば、次の3つです。
c#テキストファイルの行数をカウントするにはどうすればよいですか
テキストファイル内の行数を決定します
行を速く数える方法は?

それで、私は先に進んで、私が見つけることができる最も効率的な(少なくともメモリに関して?)方法と思われるものを使用することになりました:

_private static int countFileLines(string filePath)
{
    using (StreamReader r = new StreamReader(filePath))
    {
        int i = 0;
        while (r.ReadLine() != null) 
        { 
            i++; 
        }
        return i;
    }
}
_

しかし、ファイルの行自体が非常に長い場合、これには永遠に時間がかかります。これに対するより速い解決策は本当にありませんか?

StreamReader.Read()またはStreamReader.Peek()を使おうとしていますが、どちらかをすぐに次の行に移動させることができません(または方法がわかりません)。 'もの'(chars?text?)があります。

何かアイデアはありますか?


結論/結果(提供された回答に基づいていくつかのテストを実行した後):

以下の5つのメソッドを2つの異なるファイルでテストしたところ、一貫した結果が得られました。これは、昔ながらのStreamReader.ReadLine()が依然として最速の方法の1つであることを示しているようです...正直なところ、私は結局困惑しています。コメントと回答の議論。

ファイル#1:
サイズ:3,631 KB
行数:56,870

ファイル#1の結果は数秒で表示されます。
0.02-> ReadLineメソッド。
0.04->メソッドの読み取り。
0.29-> ReadByteメソッド。
0.25-> Readlines.Countメソッド。
0.04-> ReadWithBufferSizeメソッド。

ファイル#2:
サイズ:14,499 KB
行:213,424

ファイル#1の結果は数秒で表示されます。
0.08-> ReadLineメソッド。
0.19->メソッドの読み取り。
1.15-> ReadByteメソッド。
1.02-> Readlines.Countメソッド。
0.08-> ReadWithBufferSizeメソッド。

受け取ったすべてのフィードバックに基づいてテストした5つの方法は次のとおりです。

_private static int countWithReadLine(string filePath)
{
    using (StreamReader r = new StreamReader(filePath))
    {
    int i = 0;
    while (r.ReadLine() != null)
    {
        i++;
    }
    return i;
    }
}

private static int countWithRead(string filePath)
{
    using (StreamReader _reader = new StreamReader(filePath))
    {
    int c = 0, count = 0;
    while ((c = _reader.Read()) != -1)
    {
        if (c == 10)
        {
        count++;
        }
    }
    return count;
    }            
}

private static int countWithReadByte(string filePath)
{
    using (Stream s = new FileStream(filePath, FileMode.Open))
    {
    int i = 0;
    int b;

    b = s.ReadByte();
    while (b >= 0)
    {
        if (b == 10)
        {
        i++;
        }
        b = s.ReadByte();
    }
    return i;
    }
}

private static int countWithReadLinesCount(string filePath)
{
    return File.ReadLines(filePath).Count();
}

private static int countWithReadAndBufferSize(string filePath)
{
    int bufferSize = 512;

    using (Stream s = new FileStream(filePath, FileMode.Open))
    {
    int i = 0;
    byte[] b = new byte[bufferSize];
    int n = 0;

    n = s.Read(b, 0, bufferSize);
    while (n > 0)
    {
        i += countByteLines(b, n);
        n = s.Read(b, 0, bufferSize);
    }
    return i;
    }
}

private static int countByteLines(byte[] b, int n)
{
    int i = 0;
    for (int j = 0; j < n; j++)
    {
    if (b[j] == 10)
    {
        i++;
    }
    }

    return i;
}
_
13
sergeidave

いいえそうではありません。ポイントは-それは必要のない文字列を具体化します。

それを数えるには、「文字列」の部分を無視して「行」の部分に進む方がはるかに良いでしょう。

lINEは、\ r\n(13、10-CR LF)または別のマーカーで終わる一連のバイトです。

バッファリングされたストリームでバイトに沿って実行し、行末マーカーの出現数を数えます。

9
TomTom

これを高速に行う方法を知る最良の方法は、C/C++を使用せずにこれを行う最速の方法を考えることです。

アセンブリでは、メモリをスキャンして文字を探すCPUレベルの操作があるため、アセンブリでは次のようにします。

  • ファイルの大部分(またはすべて)をメモリに読み込みます
  • SCASBコマンドを実行します
  • 必要に応じて繰り返します

したがって、C#では、コンパイラーを可能な限りそれに近づける必要があります。

5
Hogan

私は複数の方法を試し、それらのパフォーマンスをテストしました。

1バイトを読み取る方法は、他の方法よりも約50%遅くなります。他のメソッドはすべて、ほぼ同じ時間で戻ります。スレッドを作成してこれを非同期で実行してみると、読み取りを待っている間に前の読み取りの処理を開始できます。それは私には頭痛の種のように聞こえます。

私は1つのライナーを使用します:File.ReadLines(filePath).Count();それは私がテストした他のメソッドと同様に実行します。

        private static int countFileLines(string filePath)
        {
            using (StreamReader r = new StreamReader(filePath))
            {
                int i = 0;
                while (r.ReadLine() != null)
                {
                    i++;
                }
                return i;
            }
        }

        private static int countFileLines2(string filePath)
        {
            using (Stream s = new FileStream(filePath, FileMode.Open))
            {
                int i = 0;
                int b;

                b = s.ReadByte();
                while (b >= 0)
                {
                    if (b == 10)
                    {
                        i++;
                    }
                    b = s.ReadByte();
                }
                return i + 1;
            }
        }

        private static int countFileLines3(string filePath)
        {
            using (Stream s = new FileStream(filePath, FileMode.Open))
            {
                int i = 0;
                byte[] b = new byte[bufferSize];
                int n = 0;

                n = s.Read(b, 0, bufferSize);
                while (n > 0)
                {
                    i += countByteLines(b, n);
                    n = s.Read(b, 0, bufferSize);
                }
                return i + 1;
            }
        }

        private static int countByteLines(byte[] b, int n)
        {
            int i = 0;
            for (int j = 0; j < n; j++)
            {
                if (b[j] == 10)
                {
                    i++;
                }
            }

            return i;
        }

        private static int countFileLines4(string filePath)
        {
            return File.ReadLines(filePath).Count();
        }
4
Nick Bray
public static int CountLines(Stream stm)
{
    StreamReader _reader = new StreamReader(stm);
    int c = 0, count = 0;
    while ((c = _reader.Read()) != -1)
    {
        if (c == '\n')
        {
            count++;
        }
    }
    return count;
}
3
Brian

はい、そのような行を読むことは、実用的な意味で最も速くて簡単な方法です。

ここにショートカットはありません。ファイルは行ベースではないため、ファイルからすべてのバイトを読み取って、行数を判別する必要があります。

TomTomが指摘したように、行を数えるために文字列を作成する必要は厳密にはありませんが、費やされる時間の大部分は、データがディスクから読み取られるのを待つことになります。はるかに複雑なアルゴリズムを作成すると、実行時間の1%が削減され、コードの作成とテストにかかる時間が大幅に増加します。

3
Guffa

ファイルを読み取る方法は多数あります。通常、最速の方法が最も簡単です。

using (StreamReader sr = File.OpenText(fileName))
{
        string s = String.Empty;
        while ((s = sr.ReadLine()) != null)
        {
               //do what you gotta do here
        }
}

このページは優れたパフォーマンス比較を行います BufferedReadersの使用、StringBuilderオブジェクトへの読み込み、配列全体への読み込みなど、いくつかの異なる手法の間で。

1
user3810913

StreamReaderは、バイトを文字にエンコードする際のオーバーヘッドが小さいため、一般にファイルを読み取る最速の方法ではありません。したがって、バイト配列でファイルを読み取る方が高速です。
キャッシュやその他のプロセスにより、毎回得られる結果は少し異なりますが、16 MBのファイルで得られた結果の1つ(ミリ秒単位)は次のとおりです。

75 ReadLines 
82 ReadLine 
22 ReadAllBytes 
23 Read 32K 
21 Read 64K 
27 Read 128K 

一般に、File.ReadLinesStreamReader.ReadLineループよりも少し遅いはずです。 File.ReadAllBytesは、ファイルが大きいと遅くなり、ファイルが大きいとメモリ不足の例外がスローされます。 FileStreamのデフォルトのバッファサイズは4Kですが、私のマシンでは64Kが最速のようでした。

    private static int countWithReadLines(string filePath)
    {
        int count = 0;
        var lines = File.ReadLines(filePath);

        foreach (var line in lines) count++;
        return count;
    }

    private static int countWithReadLine(string filePath)
    {
        int count = 0;
        using (var sr = new StreamReader(filePath))      
            while (sr.ReadLine() != null)
                count++;
        return count;
    }

    private static int countWithFileStream(string filePath, int bufferSize = 1024 * 4)
    {
        using (var fs = new FileStream(filePath, FileMode.Open, FileAccess.Read))
        {
            int count = 0;
            byte[] array = new byte[bufferSize];

            while (true)
            {
                int length = fs.Read(array, 0, bufferSize);

                for (int i = 0; i < length; i++)
                    if(array[i] == 10)
                        count++;

                if (length < bufferSize) return count;
            }
        } // end of using
    }

そしてテストされた:

var path = "1234567890.txt"; Stopwatch sw; string s = "";
File.WriteAllLines(path, Enumerable.Repeat("1234567890abcd", 1024 * 1024 )); // 16MB (16 bytes per line)

sw = Stopwatch.StartNew(); countWithReadLines(path)   ; sw.Stop(); s += sw.ElapsedMilliseconds + " ReadLines \n";
sw = Stopwatch.StartNew(); countWithReadLine(path)    ; sw.Stop(); s += sw.ElapsedMilliseconds + " ReadLine \n";
sw = Stopwatch.StartNew(); countWithReadAllBytes(path); sw.Stop(); s += sw.ElapsedMilliseconds + " ReadAllBytes \n";

sw = Stopwatch.StartNew(); countWithFileStream(path, 1024 * 32); sw.Stop(); s += sw.ElapsedMilliseconds + " Read 32K \n";
sw = Stopwatch.StartNew(); countWithFileStream(path, 1024 * 64); sw.Stop(); s += sw.ElapsedMilliseconds + " Read 64K \n";
sw = Stopwatch.StartNew(); countWithFileStream(path, 1024 *128); sw.Stop(); s += sw.ElapsedMilliseconds + " Read 128K \n";

MessageBox.Show(s);
0
Slai