Apa cara tercepat untuk membaca file teks baris demi baris?

319

Saya ingin membaca file teks baris demi baris. Saya ingin tahu apakah saya melakukannya seefisien mungkin dalam lingkup .NET C #.

Inilah yang saya coba sejauh ini:

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
}
Loren C Fortner
sumber
7
Dengan Fastestmaksud dari kinerja atau pengembangan perspektif?
sll
1
Ini akan mengunci file selama durasi metode. Anda bisa menggunakan File.ReadAllLines ke dalam array kemudian memproses array.
Kell
17
BTW, lampirkan filestream = new FileStreamdalam using()pernyataan untuk menghindari kemungkinan masalah yang mengganggu dengan penguncian file yang dikunci
sll
Mengenai melampirkan FileStream menggunakan pernyataan (), lihat StackOverflow tentang metode yang direkomendasikan: StackOverflow menggunakan pernyataan filestream streamreader
deegee
Saya pikir ReadToEnd () lebih cepat.
Dan Gifford

Jawaban:

315

Untuk menemukan cara tercepat untuk membaca file baris demi baris, Anda harus melakukan benchmarking. Saya telah melakukan beberapa tes kecil di komputer saya, tetapi Anda tidak dapat berharap bahwa hasil saya berlaku untuk lingkungan Anda.

Menggunakan StreamReader.ReadLine

Ini pada dasarnya metode Anda. Untuk beberapa alasan Anda mengatur ukuran buffer ke nilai sekecil mungkin (128). Peningkatan ini secara umum akan meningkatkan kinerja. Ukuran default adalah 1.024 dan pilihan bagus lainnya adalah 512 (ukuran sektor di Windows) atau 4.096 (ukuran kluster di NTFS). Anda harus menjalankan patokan untuk menentukan ukuran buffer optimal. Buffer yang lebih besar adalah - jika tidak lebih cepat - setidaknya tidak lebih lambat dari buffer yang lebih kecil.

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
  }

The FileStreamkonstruktor memungkinkan Anda untuk menentukan FileOptions . Misalnya, jika Anda membaca file besar secara berurutan dari awal hingga akhir, Anda dapat mengambil manfaat dari FileOptions.SequentialScan. Sekali lagi, pembandingan adalah hal terbaik yang dapat Anda lakukan.

Menggunakan File.ReadLines

Ini sangat mirip dengan solusi Anda sendiri kecuali itu diimplementasikan menggunakan StreamReaderdengan ukuran buffer tetap 1.024. Di komputer saya ini menghasilkan kinerja yang sedikit lebih baik dibandingkan dengan kode Anda dengan ukuran buffer 128. Namun, Anda bisa mendapatkan peningkatan kinerja yang sama dengan menggunakan ukuran buffer yang lebih besar. Metode ini diimplementasikan menggunakan blok iterator dan tidak mengkonsumsi memori untuk semua lini.

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

Menggunakan File.ReadAllLines

Ini sangat mirip dengan metode sebelumnya kecuali bahwa metode ini menumbuhkan daftar string yang digunakan untuk membuat array garis yang dikembalikan sehingga persyaratan memori lebih tinggi. Namun, ia kembali String[]dan tidak IEnumerable<String>memungkinkan Anda mengakses jalur secara acak.

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

Menggunakan String.Split

Metode ini jauh lebih lambat, setidaknya pada file besar (diuji pada file 511 KB), mungkin karena cara String.Splitdiimplementasikan. Ini juga mengalokasikan array untuk semua baris yang meningkatkan memori yang diperlukan dibandingkan dengan solusi Anda.

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

Saran saya adalah menggunakan File.ReadLineskarena bersih dan efisien. Jika Anda memerlukan opsi berbagi khusus (misalnya Anda menggunakan FileShare.ReadWrite), Anda dapat menggunakan kode Anda sendiri tetapi Anda harus menambah ukuran buffer.

Martin Liversage
sumber
1
Terima kasih untuk ini - penyertaan Anda dari parameter ukuran buffer pada konstruktor StreamReader sangat membantu. Saya streaming dari Amazon S3 API, dan menggunakan ukuran buffer yang cocok mempercepat semuanya dalam hubungannya dengan ReadLine ().
Richard K.
Saya tidak mengerti. Secara teori, sebagian besar waktu yang dihabiskan untuk membaca file akan menjadi waktu mencari pada disk dan overhead aliran memanipulasi, seperti apa yang akan Anda lakukan dengan File.ReadLines. File.ReadLines, di sisi lain, seharusnya membaca semua file ke dalam memori sekaligus. Bagaimana itu bisa lebih buruk dalam kinerja?
Paling tinggi
2
Saya tidak bisa mengatakan tentang kinerja kecepatan tetapi satu hal yang pasti: itu jauh lebih buruk pada konsumsi memori. Jika Anda harus menangani file yang sangat besar (misalnya GB), ini sangat penting. Terlebih lagi jika itu berarti harus menukar memori. Di sisi kecepatan, Anda dapat menambahkan bahwa ReadAllLine perlu membaca SEMUA baris SEBELUM mengembalikan pemrosesan penundaan hasil. Dalam beberapa skenario, IMPRESI kecepatan lebih penting daripada kecepatan mentah.
bkqc
Jika Anda membaca aliran sebagai byte array Ini akan membaca file dari 20% ~ 80% lebih cepat (dari tes yang saya lakukan). Yang Anda butuhkan adalah untuk mendapatkan array byte dan mengubahnya menjadi string. Begitulah cara saya melakukannya: Untuk membaca gunakan stream.Read () Anda dapat membuat loop untuk membuatnya dibaca dalam potongan. Setelah menambahkan seluruh konten ke dalam array byte (gunakan System.Buffer.BlockCopy ) Anda harus mengubah byte menjadi string: Encoding.Default.GetString (byteContent, 0, byteContent.Length - 1) .Split (string baru [ ] {"\ r \ n", "\ r", "\ n"}, StringSplitOptions.None);
Kim Lage
200

Jika Anda menggunakan .NET 4, cukup gunakan File.ReadLinesyang melakukan semuanya untuk Anda. Saya menduga itu banyak sama seperti milik Anda, kecuali itu juga dapat menggunakan FileOptions.SequentialScandan buffer yang lebih besar (128 tampaknya sangat kecil).

Jon Skeet
sumber
Manfaat lain ReadLines()adalah malas jadi bekerja dengan baik dengan LINQ.
stt106
35

Meskipun File.ReadAllLines()merupakan salah satu cara paling sederhana untuk membaca file, itu juga salah satu yang paling lambat.

Jika Anda hanya ingin membaca baris dalam file tanpa berbuat banyak, menurut tolok ukur ini , cara tercepat untuk membaca file adalah metode lama:

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

Namun, jika Anda harus melakukan banyak hal pada setiap baris, maka artikel ini menyimpulkan bahwa cara terbaik adalah yang berikut (dan lebih cepat untuk mengalokasikan sebelumnya string [] jika Anda tahu berapa banyak baris yang akan Anda baca):

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
});
Coder Gratis 24
sumber
13

Gunakan kode berikut:

foreach (string line in File.ReadAllLines(fileName))

Ini adalah perbedaan BESAR dalam kinerja membaca.

Itu datang pada biaya konsumsi memori, tetapi benar-benar layak!

pengguna2671536
sumber
saya lebih suka File.ReadLines (klik saya) daripadaFile.ReadAllLines
newbieguy
5

Ada topik yang bagus tentang ini di pertanyaan Stack Overflow Apakah pengembalian hasil lebih lambat dari pengembalian "sekolah lama"? .

Ia mengatakan:

ReadAllLines memuat semua baris ke dalam memori dan mengembalikan string []. Semua baik dan bagus jika filenya kecil. Jika file lebih besar dari yang akan muat dalam memori, Anda akan kehabisan memori.

ReadLines, di sisi lain, menggunakan imbal hasil untuk mengembalikan satu baris sekaligus. Dengan itu, Anda dapat membaca file ukuran apa pun. Itu tidak memuat seluruh file ke dalam memori.

Katakan Anda ingin menemukan baris pertama yang berisi kata "foo", dan kemudian keluar. Menggunakan ReadAllLines, Anda harus membaca seluruh file ke dalam memori, bahkan jika "foo" muncul di baris pertama. Dengan ReadLines, Anda hanya membaca satu baris. Yang mana yang lebih cepat?

Marcel James
sumber
4

Jika ukuran file tidak besar, maka lebih cepat membaca seluruh file dan membaginya setelahnya

var filestreams = sr.ReadToEnd().Split(Environment.NewLine, 
                              StringSplitOptions.RemoveEmptyEntries);
Saeed Amiri
sumber
6
File.ReadAllLines()
jgauffin
@ jgauffin Saya tidak tahu di balik implementasi file.ReadAlllines () tapi saya pikir itu memiliki buffer terbatas dan buffer fileReadtoEnd harus lebih besar, sehingga jumlah akses ke file akan berkurang dengan cara ini, dan melakukan string. ukuran file case tidak besar lebih cepat dari multiple access to file.
Saeed Amiri
Saya ragu yang File.ReadAllLinesmemiliki ukuran buffer tetap karena ukuran file diketahui.
jgauffin
1
@ jgauffin: Dalam. NET 4.0 File.ReadAllLinesmembuat daftar dan menambahkan daftar ini dalam satu lingkaran menggunakan StreamReader.ReadLine(dengan potensi realokasi array yang mendasarinya). Metode ini menggunakan ukuran buffer standar 1024. StreamReader.ReadToEndMenghindari bagian parsing garis dan ukuran buffer dapat diatur dalam konstruktor jika diinginkan.
Martin Liversage
Akan sangat membantu untuk mendefinisikan "BESAR" dalam hal ukuran file.
Paul
2

Jika Anda memiliki cukup memori, saya telah menemukan beberapa peningkatan kinerja dengan membaca seluruh file ke dalam aliran memori , dan kemudian membuka pembaca aliran untuk membaca baris. Selama Anda benar-benar berencana membaca seluruh file, ini dapat menghasilkan beberapa perbaikan.

Kibbee
sumber
1
File.ReadAllLinestampaknya menjadi pilihan yang lebih baik.
jgauffin
2

Anda tidak bisa mendapatkan lebih cepat jika Anda ingin menggunakan API yang ada untuk membaca baris. Tetapi membaca potongan yang lebih besar dan secara manual menemukan setiap baris baru di buffer baca mungkin akan lebih cepat.

jgauffin
sumber