Bir metin dosyasını satır satır okumanın en hızlı yolu nedir?


319

Bir metin dosyasını satır satır okumak istiyorum. Bir şeylerin .NET C # kapsamında mümkün olduğunca verimli bir şekilde yaptım mı bilmek istedim.

Şimdiye kadar deniyorum:

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
By Fastestsize performans veya geliştirme perspektiflerden demek?
sll

1
Bu işlem yöntem boyunca dosyayı kilitleyecektir. File.ReadAllLines öğesini bir dizide kullanabilir ve sonra diziyi işleyebilirsiniz.
Kell

17
BTW, enclose filestream = new FileStreamiçinde using()açıklamaya kilitlenmiş dosya saplı olası can sıkıcı sorunları önlemek için
sll

FileStream'in () ifadesini
kapsamaya

Bence ReadToEnd () daha hızlı.
Dan Gifford

Yanıtlar:


315

Bir dosyayı satır satır okumanın en hızlı yolunu bulmak için bazı kıyaslamalar yapmanız gerekir. Bilgisayarımda bazı küçük testler yaptım, ancak sonuçlarımın ortamınız için geçerli olmasını bekleyemezsiniz.

StreamReader.ReadLine kullanma

Bu temel olarak sizin yönteminizdir. Herhangi bir nedenle arabellek boyutunu mümkün olan en küçük değere ayarlarsınız (128). Bunu artırmak genel olarak performansı artıracaktır. Varsayılan boyut 1.024 ve diğer iyi seçenekler 512 (Windows'ta sektör boyutu) veya 4.096 (NTFS'de küme boyutu) şeklindedir. En uygun arabellek boyutunu belirlemek için bir karşılaştırma ölçütü çalıştırmanız gerekecektir. Daha büyük bir tampon - daha hızlı değilse - en azından daha küçük bir tampondan daha yavaş değildir.

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
  }

FileStreamYapıcı belirlemenizi sağlar FileOptions . Örneğin, başından sonuna kadar büyük bir dosyayı okuyorsanız, bundan yararlanabilirsiniz FileOptions.SequentialScan. Yine, kıyaslama yapabileceğiniz en iyi şeydir.

File.ReadLines kullanma

Bu, kendi çözümünüze çok benzer, ancak StreamReader1.024 sabit tampon boyutu ile uygulanmasıdır . Bilgisayarımda bu, 128 arabellek boyutuna sahip kodunuzla karşılaştırıldığında biraz daha iyi performans sağlar. Ancak, daha büyük bir arabellek boyutu kullanarak aynı performans artışını elde edebilirsiniz. Bu yöntem bir yineleyici bloğu kullanılarak uygulanır ve tüm satırlar için bellek tüketmez.

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

File.ReadAllLines kullanma

Bu, önceki yönteme çok benzer, ancak bu yöntemin, döndürülen satır dizisini oluşturmak için kullanılan dizelerin bir listesini büyütmesi, böylece bellek gereksinimleri daha yüksektir. Ancak, satırlara rastgele erişmenize izin String[]vermez , döner IEnumerable<String>.

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

String.Split Kullanımı

Bu yöntem, büyük olasılıkla nasıl String.Splituygulandığından dolayı, en azından büyük dosyalarda (511 KB dosyada test edilmiştir) oldukça yavaştır . Ayrıca, çözümünüzle karşılaştırıldığında gerekli belleği artıran tüm satırlar için bir dizi ayırır.

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

Benim önerim, File.ReadLinestemiz ve verimli olduğu için kullanmaktır . Özel paylaşım seçeneklerine (örneğin FileShare.ReadWrite, kullandığınızda ) ihtiyacınız varsa, kendi kodunuzu kullanabilirsiniz, ancak arabellek boyutunu artırmalısınız.


1
Bunun için teşekkürler - StreamReader'ın yapıcısına arabellek boyutu parametresini eklemeniz gerçekten yardımcı oldu. Amazon'un S3 API'sinden akış yapıyorum ve eşleşen bir arabellek boyutu ReadLine () ile birlikte işleri önemli ölçüde hızlandırıyor.
Richard K.

Anlamıyorum. Teorik olarak, dosyayı okumak için harcanan zamanın büyük çoğunluğu, diskte arama zamanı ve Dosya.ReadLines ile yapacağınız gibi akışların manipülasyonu yükleri olacaktır. Öte yandan File.ReadLines, bir dosyanın her şeyi bir seferde belleğe okumalıdır. Performansta nasıl daha kötü olabilir?
h9uest

2
Hız performansı hakkında söyleyemem ama kesin olan bir şey var: bellek tüketimi konusunda çok daha kötü. Çok büyük dosyaları işlemeniz gerekiyorsa (örneğin GB), bu çok kritiktir. Daha fazla bellek değiştirmek zorunda olduğu anlamına gelir. Hız tarafında, ReadAllLine'ın sonuç geciktirme işlemini döndürmeden ÖNCE TÜM satırları okuması gerektiğini ekleyebilirsiniz. Bazı senaryolarda, hızın ETKİSİ ham hızdan daha önemlidir.
bkqc

Akışı bayt dizileri olarak okursanız Dosyayı % 20 ~% 80 daha hızlı okuyacaktır (yaptığım testlerden). İhtiyacınız olan şey bayt dizisini alıp dizeye dönüştürmektir. Ben böyle yaptım: Okumak için stream.Read () kullanın. Parçalar halinde okumak için bir döngü yapabilirsiniz. Tüm içeriği bir bayt dizisine ekledikten sonra ( System.Buffer.BlockCopy kullanın ) baytları dizeye dönüştürmeniz gerekir: Encoding.Default.GetString (byteContent, 0, byteContent.Length - 1) .Split (yeni dize [ ] {"\ r \ n", "\ r", "\ n"}, StringSplitOptions.None);
Kim Lage

200

.NET 4 kullanıyorsanız File.ReadLines, bunların hepsini sizin için yapın. Ben şüpheli kadar da kullanabilir hariç sizinkiyle aynı FileOptions.SequentialScanve daha geniş bir tampon (128 çok küçük görünüyor).


Başka bir yararı ReadLines()tembel yani LINQ ile iyi çalışıyor olmasıdır.
stt106

35

File.ReadAllLines()Bir dosyayı okumanın en basit yollarından biri olsa da, aynı zamanda en yavaşlarından biridir.

Bir dosyadaki satırları fazla bir şey yapmadan okumak istiyorsanız, bu kriterlere göre , bir dosyayı okumanın en hızlı yolu eski yöntemdir:

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

Ancak, her satırla çok şey yapmanız gerekiyorsa, bu makalede en iyi yolun şu olduğu sonucuna varılır (ve kaç satır okuyacağınızı biliyorsanız [] bir dizeyi önceden ayırmak daha hızlıdır):

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

13

Aşağıdaki kodu kullanın:

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

Bu okuma performansında çok büyük bir farktı.

Bellek tüketimi pahasına geliyor, ama tamamen buna değer!


i tercih ediyorum File.ReadLines (me tıklayın) dahaFile.ReadAllLines
newbieguy

5

Yığın Taşması sorusunda bu konuda iyi bir konu var 'Verim getirisi' "eski okul" getirisinden daha yavaş mı? .

Diyor ki:

ReadAllLines tüm satırları belleğe yükler ve bir [] dizesi döndürür. Her şey iyi ve dosya küçükse. Dosya belleğe sığmayacak kadar büyükse, bellekte yer kalmaz.

Öte yandan ReadLines, bir seferde bir satır döndürmek için verim dönüşünü kullanır. Bununla birlikte, herhangi bir boyut dosyasını okuyabilirsiniz. Tüm dosyayı belleğe yüklemez.

"Foo" kelimesini içeren ilk satırı bulmak istediğinizi söyleyin ve çıkın. ReadAllLines kullanarak, ilk satırda "foo" oluşsa bile dosyanın tamamını belleğe okumalısınız. ReadLines ile yalnızca bir satır okursunuz. Hangisi daha hızlı olurdu?


4

Dosya boyutu büyük değilse, tüm dosyayı okumak ve daha sonra bölmek daha hızlıdır

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

6
File.ReadAllLines()
jgauffin

@jgauffin file.ReadAlllines () uygulaması arkasında bilmiyorum ama sınırlı bir tampon ve fileReadtoEnd arabellek daha büyük olması gerektiğini düşünüyorum, bu yüzden dosyaya erişim sayısı bu şekilde azalacak ve string yapıyor. büyük / küçük boyutlu dosya boyutu, dosyaya birden çok erişimden daha hızlıdır.
Saeed Amiri

File.ReadAllLinesDosya boyutu bilindiği için sabit bir arabellek boyutuna sahip olduğundan şüpheliyim .
jgauffin

1
@jgauffin: .NET 4.0'da File.ReadAllLinesbir liste oluşturur ve bu listeye StreamReader.ReadLine(altta yatan dizinin olası yeniden tahsisi ile ) bir döngü içinde eklenir . Bu yöntem 1024 varsayılan tampon boyutunu kullanır. StreamReader.ReadToEndSatır ayrıştırma parçasını önler ve tampon boyutu istenirse yapıcıda ayarlanabilir.
Martin Liversage

Dosya boyutu ile ilgili olarak "BÜYÜK" tanımlamak yararlı olacaktır.
Paul

2

Yeterli belleğiniz varsa, tüm dosyayı bir bellek akışına okuyarak ve ardından satırları okumak için bir akış okuyucu açarak bazı performans kazanımları buldum . Aslında tüm dosyayı okumayı planladığınız sürece bu bazı iyileştirmeler sağlayabilir.


1
File.ReadAllLineso zaman daha iyi bir seçim gibi görünüyor.
jgauffin

2

Satırları okumak için mevcut bir API'yi kullanmak istiyorsanız daha hızlı olamazsınız. Ancak daha büyük parçaları okumak ve her yeni satırı manuel olarak okuma arabelleğinde bulmak muhtemelen daha hızlı olacaktır.

Sitemizi kullandığınızda şunları okuyup anladığınızı kabul etmiş olursunuz: Çerez Politikası ve Gizlilik Politikası.
Licensed under cc by-sa 3.0 with attribution required.