テキストファイルを1行ずつ読み取る最も速い方法は何ですか。


319

テキストファイルを1行ずつ読みたいのですが。.NET C#のスコープ内で可能な限り効率的に実行しているかどうかを知りたいと思いました。

これは私がこれまでに試みていることです:

var filestream = new System.IO.FileStream(textFilePath,
                                          System.IO.FileMode.Open,
                                          System.IO.FileAccess.Read,
                                          System.IO.FileShare.ReadWrite);
var file = new System.IO.StreamReader(filestream, System.Text.Encoding.UTF8, true, 128);

while ((lineOfText = file.ReadLine()) != null)
{
    //Do something with the lineOfText
}

7
することでFastest、あなたのパフォーマンスや開発の視点から意味ですか?
sll

1
これにより、メソッドの実行中はファイルがロックされます。File.ReadAllLinesを配列に使用して、配列を処理できます。
Kell

17
ところで、ロックされたファイルハンドルで発生する可能性のある迷惑な問題を回避するためにfilestream = new FileStreamusing()ステートメントで囲みます
sll


ReadToEnd()の方が速いと思います。
Dan Gifford、2015

回答:


315

ファイルを1行ずつ読み取る最速の方法を見つけるには、いくつかのベンチマークを行う必要があります。私は自分のコンピューターでいくつかの小さなテストを行いましたが、私の結果があなたの環境に当てはまるとは期待できません。

StreamReader.ReadLineの使用

これは基本的にあなたの方法です。何らかの理由で、バッファサイズを可能な最小値(128)に設定しました。これを増やすと、一般にパフォーマンスが向上します。デフォルトのサイズは1,024で、その他の適切な選択肢は512(Windowsのセクターサイズ)または4,096(NTFSのクラスターサイズ)です。最適なバッファーサイズを決定するには、ベンチマークを実行する必要があります。大きいバッファは、高速ではないにしても、少なくとも小さいバッファより遅くはありません。

const Int32 BufferSize = 128;
using (var fileStream = File.OpenRead(fileName))
  using (var streamReader = new StreamReader(fileStream, Encoding.UTF8, true, BufferSize)) {
    String line;
    while ((line = streamReader.ReadLine()) != null)
      // Process line
  }

FileStreamコンストラクタは、指定することができますFileOptionsはを。たとえば、大きなファイルを最初から最後まで順番に読み取る場合は、を利用すると便利ですFileOptions.SequentialScan。繰り返しになりますが、ベンチマークはあなたができる最高のことです。

File.ReadLinesの使用

これはStreamReader、固定バッファサイズ1,024 を使用して実装されることを除いて、独自のソリューションと非常に似ています。私のコンピューターでは、バッファーサイズが128のコードと比較して、パフォーマンスがわずかに向上しています。ただし、より大きなバッファーサイズを使用することで、同じパフォーマンスの向上を得ることができます。このメソッドはイテレーターブロックを使用して実装され、すべての行のメモリを消費しません。

var lines = File.ReadLines(fileName);
foreach (var line in lines)
  // Process line

File.ReadAllLinesの使用

これは前の方法と非常によく似ていますが、この方法では、返される行の配列の作成に使用される文字列のリストが大きくなるため、メモリ要件が高くなります。ただし、戻り値String[]であり、IEnumerable<String>ランダムに行にアクセスすることはできません。

var lines = File.ReadAllLines(fileName);
for (var i = 0; i < lines.Length; i += 1) {
  var line = lines[i];
  // Process line
}

String.Splitの使用

おそらくString.Split実装方法が原因で、少なくとも大きなファイル(511 KBのファイルでテスト済み)では、この方法はかなり遅くなります。また、すべての行に配列を割り当て、ソリューションと比較して必要なメモリを増やします。

using (var streamReader = File.OpenText(fileName)) {
  var lines = streamReader.ReadToEnd().Split("\r\n".ToCharArray(), StringSplitOptions.RemoveEmptyEntries);
  foreach (var line in lines)
    // Process line
}

File.ReadLines清潔で効率的であるため、使用することをお勧めします。特別な共有オプションが必要な場合(たとえばを使用する場合FileShare.ReadWrite)、独自のコードを使用できますが、バッファーサイズを増やす必要があります。


1
これをありがとう-StreamReaderのコンストラクターにバッファーサイズパラメーターを含めることは本当に役に立ちました。私はAmazonのS3 APIからストリーミングしています。一致するバッファーサイズを使用すると、ReadLine()との併用でかなり高速化されます。
Richard K.

分かりません。理論的には、ファイルの読み取りに費やされる時間の大部分は、ディスクでのシーク時間と、File.ReadLinesで行うのと同様に、ストリームを操作するオーバーヘッドです。一方、File.ReadLinesは、ファイルのすべてを一度にメモリに読み込むことになっています。パフォーマンスがどのように悪化するのでしょうか?
h9uest 2015年

2
スピードパフォーマンスについては言えませんが、1つ確かなことがあります。それは、メモリ消費がはるかに悪いことです。非常に大きなファイル(GBなど)を処理する必要がある場合、これは非常に重要です。それがメモリを交換する必要があることを意味する場合はなおさらです。速度の面では、ReadAllLineが処理を遅らせる結果を返す前にすべての行を読み取る必要があることを追加できます。一部のシナリオでは、速度の印象が生の速度よりも重要です。
bkqc 2016

ストリームをバイト配列として読み取る場合、ファイルを20%〜80%高速に読み取ります(私が行ったテストから)。必要なのは、バイト配列を取得して文字列に変換することです。それが私がやった方法です:読み取りにはstream.Read()を使用 します。ループを作成して、チャンクで読み取るようにすることができます。コンテンツ全体をバイト配列に追加した後(System.Buffer.BlockCopyを使用)、バイトを文字列に変換する必要があります:Encoding.Default.GetString(byteContent、0、byteContent.Length-1).Split(new string [ ] {"\ r \ n"、 "\ r"、 "\ n"}、StringSplitOptions.None);
Kim Lage

200

.NET 4を使用File.ReadLinesしている場合は、すべてを実行するのに使用します。私はそれがだ疑いくらい、それはまた、使用することを除いて、あなたと同じFileOptions.SequentialScanと大きなバッファ(128は非常に小さいと思われます)。


もう1つの利点ReadLines()は、遅延が発生するため、LINQでうまく機能することです。
stt106 2017

35

一方でFile.ReadAllLines()、ファイルを読み込むための最も簡単な方法の一つであり、それはまた、最も遅いの一つです。

何もせずにファイルの行を読みたいだけなら、 これらのベンチマークよればを読み取る最も速い方法は、次の古い方法です。

using (StreamReader sr = File.OpenText(fileName))
{
        string s = String.Empty;
        while ((s = sr.ReadLine()) != null)
        {
               //do minimal amount of work here
        }
}

ただし、各行で多くのことを行う必要がある場合、この記事では、最善の方法は次のとおりであると結論付けています(読み取る行数がわかっている場合は、string []を事前に割り当てる方が高速です)。

AllLines = new string[MAX]; //only allocate memory here

using (StreamReader sr = File.OpenText(fileName))
{
        int x = 0;
        while (!sr.EndOfStream)
        {
               AllLines[x] = sr.ReadLine();
               x += 1;
        }
} //Finished. Close the file

//Now parallel process each line in the file
Parallel.For(0, AllLines.Length, x =>
{
    DoYourStuff(AllLines[x]); //do your work here
});


5

スタックオーバーフローの質問に、これに関する良いトピックがあります。

それは言う:

ReadAllLinesは、すべての行をメモリに読み込み、string []を返します。ファイルが小さい場合は問題ありません。ファイルがメモリに収まるよりも大きい場合、メモリが不足します。

一方、ReadLinesは、yield returnを使用して一度に1行を返します。それを使用すると、任意のサイズのファイルを読み取ることができます。ファイル全体をメモリにロードしません。

「foo」という単語を含む最初の行を検索して終了したいとします。ReadAllLinesを使用すると、最初の行に「foo」が発生した場合でも、ファイル全体をメモリに読み込む必要があります。ReadLinesを使用すると、1行しか読み取れません。どちらが速いですか?


4

ファイルサイズが大きくない場合は、ファイル全体を読み取って後で分割する方が高速です

var filestreams = sr.ReadToEnd().Split(Environment.NewLine, 
                              StringSplitOptions.RemoveEmptyEntries);

6
File.ReadAllLines()
jgauffin

@jgauffin file.ReadAlllines()の実装の背後にあることはわかりませんが、制限されたバッファーがあり、fileReadtoEndバッファーはもっと大きいはずなので、この方法でファイルへのアクセス数を減らし、string.Splitを実行しますファイルサイズが大きくない場合は、ファイルへの複数のアクセスよりも高速です。
Saeed Amiri、2011年

File.ReadAllLinesファイルサイズがわかっているので、バッファサイズが固定されているとは思いません。
jgauffin

1
@jgauffin:.NET 4.0ではFile.ReadAllLines、リストを作成し、StreamReader.ReadLine(基になる配列の再割り当ての可能性がある)を使用してループでこのリストに追加します。このメソッドは、デフォルトのバッファーサイズ1024を使用しますStreamReader.ReadToEnd。必要に応じて、行の解析部分を回避し、バッファーサイズをコンストラクターで設定できます。
マーティンLiversage

ファイルサイズに関して「BIG」を定義すると役立ちます。
ポール

2

十分なメモリがある場合は、ファイル全体をメモリストリームに読み取り、その上でストリームリーダーを開いて行を読み取ると、パフォーマンスが向上することがわかりました。とにかく実際にファイル全体を読み取ることを計画している限り、これはいくつかの改善をもたらすことができます。


1
File.ReadAllLinesそのときはより良い選択のようです。
jgauffin

2

既存のAPIを使用して行を読み取る場合、これ以上速くすることはできません。ただし、より大きなチャンクを読み取って、読み取りバッファで新しい各行を手動で見つける方がおそらく高速です。

弊社のサイトを使用することにより、あなたは弊社のクッキーポリシーおよびプライバシーポリシーを読み、理解したものとみなされます。
Licensed under cc by-sa 3.0 with attribution required.