Jaki jest najszybszy sposób czytania pliku tekstowego wiersz po wierszu?

318

Chcę czytać plik tekstowy wiersz po wierszu. Chciałem wiedzieć, czy robię to tak skutecznie, jak to możliwe w zakresie .NET C # rzeczy.

Do tej pory próbuję:

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
źródło
7
Przez Fastestciebie znaczy od wydajności lub rozwojowych perspektyw?
sll
1
Spowoduje to zablokowanie pliku na czas trwania metody. Możesz użyć File.ReadAllLines do tablicy, a następnie przetworzyć tablicę.
Kell,
17
BTW, załączyć filestream = new FileStreamw using()oświadczeniu, aby uniknąć możliwych irytujących problemów z zablokowanym uchwytem pliku
sll
Jeśli chodzi o dołączanie instrukcji FileStream za pomocą instrukcji (), zobacz StackOverflow w sprawie zalecanej metody: StackOverflow za pomocą instrukcji streamester instrukcji strumienia plików
deegee
Myślę, że ReadToEnd () jest szybszy.
Dan Gifford,

Odpowiedzi:

315

Aby znaleźć najszybszy sposób na odczytanie pliku linia po linii, będziesz musiał przeprowadzić testy porównawcze. Zrobiłem kilka małych testów na moim komputerze, ale nie można oczekiwać, że moje wyniki będą miały zastosowanie w twoim środowisku.

Korzystanie ze StreamReader.ReadLine

To jest w zasadzie twoja metoda. Z jakiegoś powodu ustawiłeś rozmiar bufora na najmniejszą możliwą wartość (128). Zwiększenie tego ogólnie zwiększy wydajność. Domyślny rozmiar to 1024, a inne dobre wybory to 512 (rozmiar sektora w systemie Windows) lub 4096 (rozmiar klastra w systemie plików NTFS). Będziesz musiał uruchomić test porównawczy, aby określić optymalny rozmiar bufora. Większy bufor jest - jeśli nie szybszy - przynajmniej nie wolniejszy niż mniejszy bufor.

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
  }

FileStreamKonstruktor pozwala określić FileOptions . Na przykład, jeśli czytasz duży plik sekwencyjnie od początku do końca, możesz skorzystać FileOptions.SequentialScan. Ponownie, analiza porównawcza jest najlepszą rzeczą, jaką możesz zrobić.

Korzystanie z File.ReadLines

Jest to bardzo podobne do twojego własnego rozwiązania, z tym wyjątkiem, że jest implementowane przy użyciu StreamReaderbufora o stałej wielkości 1024. Na moim komputerze powoduje to nieco lepszą wydajność w porównaniu z kodem o rozmiarze bufora 128. Jednak ten sam wzrost wydajności można uzyskać, stosując większy rozmiar bufora. Ta metoda jest implementowana przy użyciu bloku iteratora i nie zużywa pamięci dla wszystkich linii.

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

Korzystanie z File.ReadAllLines

Jest to bardzo podobne do poprzedniej metody, z tą różnicą, że metoda ta powiększa listę ciągów znaków używanych do utworzenia zwróconej tablicy wierszy, więc wymagania dotyczące pamięci są wyższe. Jednak zwraca, String[]a nie IEnumerable<String>pozwala na losowy dostęp do linii.

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

Korzystanie z String.Split

Ta metoda jest znacznie wolniejsza, przynajmniej w przypadku dużych plików (testowanych na pliku 511 KB), prawdopodobnie ze względu na sposób String.Splitimplementacji. Przydziela również tablicę dla wszystkich linii, zwiększając wymaganą pamięć w porównaniu do twojego rozwiązania.

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

Moja sugestia to użycie, File.ReadLinesponieważ jest czyste i wydajne. Jeśli potrzebujesz specjalnych opcji udostępniania (na przykład używasz FileShare.ReadWrite), możesz użyć własnego kodu, ale powinieneś zwiększyć rozmiar bufora.

Martin Liversage
źródło
1
Dzięki za to - włączenie parametru rozmiaru bufora do konstruktora StreamReadera było bardzo pomocne. Przesyłam strumieniowo z interfejsu API Amazon S3 i użycie pasującego rozmiaru bufora znacznie przyspiesza działanie w połączeniu z ReadLine ().
Richard K.
Nie rozumiem. Teoretycznie zdecydowana większość czasu spędzonego na czytaniu pliku to czas szukania na dysku i koszty manipulowania strumieniami, podobnie jak w przypadku File.ReadLines. Z drugiej strony File.ReadLines ma za jednym razem odczytywać wszystko z pliku do pamięci. Jak może być gorzej w wydajności?
h9uest
2
Nie mogę powiedzieć o wydajności, ale jedno jest pewne: jest znacznie gorsze w zużyciu pamięci. Jeśli musisz obsługiwać bardzo duże pliki (na przykład GB), jest to bardzo ważne. Tym bardziej, że oznacza to zamianę pamięci. Po stronie prędkości można dodać, że ReadAllLine musi czytać WSZYSTKIE linie PRZED zwróceniem wyniku opóźniającego przetwarzanie. W niektórych scenariuszach WRAŻENIE prędkości jest ważniejsze niż prędkość surowa.
bkqc
Jeśli czytasz strumień jako tablice bajtowe, plik będzie odczytywany o 20% ~ 80% szybciej (z testów, które zrobiłem). Musisz pobrać tablicę bajtów i przekonwertować ją na ciąg znaków. Tak to zrobiłem: do odczytu użyj stream.Read () Możesz zrobić pętlę, aby czytać fragmenty. Po dodaniu całej zawartości do tablicy bajtów (użyj System.Buffer.BlockCopy ) musisz przekonwertować bajty na ciąg znaków: Encoding.Default.GetString (byteContent, 0, byteContent.Length - 1) .Split (nowy ciąg [ ] {"\ r \ n", "\ r", "\ n"}, StringSplitOptions.None);
Kim Lage
200

Jeśli korzystasz z .NET 4, po prostu użyj, File.ReadLinesktóry zrobi wszystko za Ciebie. Podejrzewam, że jest bardzo podobny do twojego, z wyjątkiem tego, że może również używać FileOptions.SequentialScani większego bufora (128 wydaje się bardzo mały).

Jon Skeet
źródło
Kolejną zaletą ReadLines()jest to, że jest leniwy, więc działa dobrze z LINQ.
stt106,
35

Chociaż File.ReadAllLines()jest to jeden z najprostszych sposobów odczytu pliku, jest również jednym z najwolniejszych.

Jeśli chcesz tylko czytać wiersze w pliku bez robienia dużo, zgodnie z tymi testami porównawczymi , najszybszym sposobem na odczytanie pliku jest stara metoda:

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

Jeśli jednak musisz dużo zrobić z każdą linią, w tym artykule stwierdza się, że najlepszym sposobem jest następująca (i szybciej jest wstępnie przydzielić ciąg [], jeśli wiesz, ile linii zamierzasz przeczytać):

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
});
Darmowy Coder 24
źródło
13

Użyj następującego kodu:

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

To była OGROMNA różnica w wydajności czytania.

Jest to koszt zużycia pamięci, ale całkowicie warto!

użytkownik2671536
źródło
wolałbym File.ReadLines (kliknij mnie) niżFile.ReadAllLines
newbieguy
5

Dobry temat na ten temat znajduje się w pytaniu o przepełnienie stosu. Czy zwrot z inwestycji jest wolniejszy niż powrót ze starej szkoły? .

To mówi:

ReadAllLines ładuje wszystkie linie do pamięci i zwraca ciąg []. Wszystko dobrze i dobrze, jeśli plik jest mały. Jeśli plik jest większy niż zmieści się w pamięci, zabraknie pamięci.

Z drugiej strony, ReadLines używa return return do zwracania jednej linii na raz. Dzięki niemu możesz odczytać plik o dowolnym rozmiarze. Nie ładuje całego pliku do pamięci.

Powiedz, że chcesz znaleźć pierwszy wiersz zawierający słowo „foo”, a następnie wyjdź. Korzystając z ReadAllLines, musisz odczytać cały plik do pamięci, nawet jeśli w pierwszym wierszu pojawi się „foo”. Dzięki ReadLines odczytujesz tylko jedną linię. Który byłby szybszy?

Marcel James
źródło
4

Jeśli rozmiar pliku nie jest duży, szybciej jest odczytać cały plik i podzielić go później

var filestreams = sr.ReadToEnd().Split(Environment.NewLine, 
                              StringSplitOptions.RemoveEmptyEntries);
Saeed Amiri
źródło
6
File.ReadAllLines()
jgauffin,
@jgauffin Nie wiem za implementacją file.ReadAlllines (), ale myślę, że ma on ograniczony bufor, a bufor fileReadtoEnd powinien być większy, więc liczba dostępu do pliku zostanie w ten sposób zmniejszona i spowoduje wykonanie string.Split w przypadek rozmiar pliku nie jest duży jest szybszy niż wielokrotny dostęp do pliku.
Saeed Amiri,
Wątpię, czy File.ReadAllLinesmają stały rozmiar bufora, ponieważ rozmiar pliku jest znany.
jgauffin
1
@ jgauffin: w .NET 4.0 File.ReadAllLinestworzy listę i dodaje ją do pętli za pomocą StreamReader.ReadLine(z potencjalnym przeniesieniem podstawowej tablicy). Ta metoda wykorzystuje domyślny rozmiar bufora 1024. StreamReader.ReadToEndPozwala to uniknąć części parsowania linii, a rozmiar bufora można ustawić w konstruktorze, jeśli jest to pożądane.
Martin Liversage,
Pomocne byłoby zdefiniowanie „DUŻEGO” w odniesieniu do rozmiaru pliku.
Paul
2

Jeśli masz wystarczającą ilość pamięci, zauważyłem pewien wzrost wydajności, wczytując cały plik do strumienia pamięci , a następnie otwierając czytnik strumieni, aby odczytać wiersze. Tak długo, jak faktycznie planujesz odczytać cały plik, może to przynieść pewne ulepszenia.

Kibee
źródło
1
File.ReadAllLineswydaje się wtedy lepszym wyborem.
jgauffin,
2

Nie możesz dostać się szybciej, jeśli chcesz użyć istniejącego API do odczytu linii. Ale czytanie większych fragmentów i ręczne znajdowanie każdej nowej linii w buforze odczytu prawdopodobnie byłoby szybsze.

jgauffin
źródło