Mam cudowne zadanie, jak radzić sobie z dużymi plikami ładowanymi do edytora skryptów naszej aplikacji (to jest jak VBA dla naszego wewnętrznego produktu do szybkich makr). Większość plików ma około 300-400 KB, co jest dobrym ładowaniem. Ale kiedy przekraczają 100 MB, proces jest trudny (jak można się spodziewać).
Dzieje się tak, że plik jest odczytywany i umieszczany w RichTextBox, po którym jest nawigowany - nie przejmuj się zbytnio tą częścią.
Deweloper, który napisał początkowy kod, po prostu używa StreamReader i robi
[Reader].ReadToEnd()
co może zająć trochę czasu.
Moim zadaniem jest rozbicie tego fragmentu kodu, odczytanie go fragmentami do bufora i wyświetlenie paska postępu z opcją anulowania.
Niektóre założenia:
- Większość plików ma rozmiar 30-40 MB
- Zawartość pliku jest tekstowa (nie binarna), niektóre są w formacie uniksowym, a niektóre w systemie DOS.
- Po pobraniu zawartości ustalamy, jaki terminator jest używany.
- Nikt nie przejmuje się po załadowaniu czasu potrzebnego na renderowanie w bogatym polu tekstowym. To tylko wstępne ładowanie tekstu.
A teraz pytania:
- Czy mogę po prostu użyć StreamReader, a następnie sprawdzić właściwość Length (czyli ProgressMax) i wydać Read dla ustawionego rozmiaru buforu i wykonać iterację w pętli while WHILST wewnątrz procesu roboczego w tle, aby nie blokował głównego wątku interfejsu użytkownika? Następnie po zakończeniu zwróć program budujący ciąg do głównego wątku.
- Zawartość trafi do StringBuilder. czy mogę zainicjować StringBuilder z rozmiarem strumienia, jeśli długość jest dostępna?
Czy są to (Twoim zdaniem) dobre pomysły? W przeszłości miałem kilka problemów z czytaniem treści ze strumieni, ponieważ zawsze pomija ostatnie kilka bajtów lub coś w tym stylu, ale zadam inne pytanie, jeśli tak jest.
źródło
Odpowiedzi:
Możesz poprawić prędkość odczytu, używając BufferedStream, na przykład:
Aktualizacja z marca 2013 r
Niedawno napisałem kod do odczytu i przetwarzania (wyszukiwania tekstu) plików tekstowych o rozmiarze 1 GB (znacznie większych niż pliki tutaj zaangażowane) i osiągnąłem znaczny wzrost wydajności, używając wzorca producent / konsument. Zadanie producenta czytało wiersze tekstu za pomocą
BufferedStream
i przekazało je osobnemu zadaniu konsumenta, które przeprowadziło wyszukiwanie.Wykorzystałem to jako okazję do nauczenia się TPL Dataflow, który bardzo dobrze nadaje się do szybkiego kodowania tego wzorca.
Dlaczego BufferedStream jest szybszy
AKTUALIZACJA z grudnia 2014 r .: Twój przebieg może się różnić
Na podstawie komentarzy FileStream powinien wewnętrznie używać BufferedStream . Kiedy po raz pierwszy podano tę odpowiedź, zmierzyłem znaczny wzrost wydajności, dodając BufferedStream. W tamtym czasie celowałem w .NET 3.x na platformie 32-bitowej. Dzisiaj, gdy celuję w .NET 4.5 na platformie 64-bitowej, nie widzę żadnej poprawy.
Związane z
Natknąłem się na przypadek, w którym przesyłanie strumieniowe dużego, wygenerowanego pliku CSV do strumienia odpowiedzi z akcji ASP.Net MVC było bardzo wolne. Dodanie BufferedStream poprawiło wydajność 100x w tym przypadku. Aby uzyskać więcej informacji, zobacz Niezbuforowane wyjście bardzo wolne
źródło
Jeśli przeczytasz statystyki wydajności i testów porównawczych w tej witrynie , zobaczysz, że najszybszym sposobem odczytania (ponieważ czytanie, pisanie i przetwarzanie są różne) pliku tekstowego jest następujący fragment kodu:
W sumie około 9 różnych metod zostało oznaczonych jako benchmark, ale ta wydaje się być lepsza przez większość czasu, nawet w przypadku czytelnika buforowanego, jak wspominali inni czytelnicy.
źródło
StringBuilder
do ładowania ich do pamięci, ładuje się szybciej, ponieważ nie tworzy nowego ciągu za każdym razem, gdy dodajesz znaki)Mówisz, że zostałeś poproszony o wyświetlenie paska postępu podczas ładowania dużego pliku. Czy to dlatego, że użytkownicy naprawdę chcą zobaczyć dokładny procent ładowania plików, czy po prostu dlatego, że chcą wizualnej informacji zwrotnej, że coś się dzieje?
Jeśli to ostatnie jest prawdą, rozwiązanie staje się znacznie prostsze. Po prostu zrób
reader.ReadToEnd()
to w wątku w tle i wyświetlaj pasek postępu typu marquee zamiast prawidłowego.Podnoszę tę kwestię, ponieważ z mojego doświadczenia wynika, że często tak jest. Kiedy piszesz program do przetwarzania danych, użytkownicy z pewnością będą zainteresowani% pełną liczbą, ale w przypadku prostych, ale powolnych aktualizacji interfejsu użytkownika bardziej prawdopodobne jest, że będą chcieli po prostu wiedzieć, że komputer się nie zawiesił. :-)
źródło
StreamReader
pętli. Jednak nadal będzie to prostsze, ponieważ nie trzeba czytać z wyprzedzeniem, aby obliczyć wskaźnik postępu.W przypadku plików binarnych najszybszym sposobem ich odczytania jest to.
W moich testach jest setki razy szybszy.
źródło
Użyj pracownika w tle i czytaj tylko ograniczoną liczbę wierszy. Czytaj więcej tylko wtedy, gdy użytkownik przewija.
I staraj się nigdy nie używać ReadToEnd (). Jest to jedna z funkcji, o której myślisz „dlaczego to zrobili?”; to pomocnik skryptów dla dzieciaków, który pasuje do małych rzeczy, ale jak widzisz, jest do bani w przypadku dużych plików ...
Ci faceci, którzy mówią ci, żebyś używał StringBuilder, muszą częściej czytać MSDN:
Zagadnienia dotyczące wydajności
Metody Concat i AppendFormat łączą nowe dane z istniejącym obiektem String lub StringBuilder. Operacja konkatenacji obiektu typu String zawsze tworzy nowy obiekt na podstawie istniejącego ciągu i nowych danych. Obiekt StringBuilder utrzymuje bufor, aby pomieścić konkatenację nowych danych. Nowe dane są dodawane na końcu bufora, jeśli miejsce jest dostępne; w przeciwnym razie przydzielany jest nowy, większy bufor, dane z oryginalnego bufora są kopiowane do nowego bufora, a następnie nowe dane są dołączane do nowego bufora. Wydajność operacji konkatenacji dla obiektu String lub StringBuilder zależy od tego, jak często występuje alokacja pamięci.
Operacja konkatenacji String zawsze przydziela pamięć, podczas gdy operacja konkatenacji StringBuilder przydziela pamięć tylko wtedy, gdy bufor obiektu StringBuilder jest zbyt mały, aby pomieścić nowe dane. W związku z tym klasa String jest preferowana w przypadku operacji konkatenacji, jeśli łączona jest stała liczba obiektów String. W takim przypadku poszczególne operacje konkatenacji mogą nawet zostać połączone w jedną operację przez kompilator. Obiekt StringBuilder jest preferowany w przypadku operacji konkatenacji, jeśli łączona jest dowolna liczba ciągów; na przykład, jeśli pętla łączy losową liczbę ciągów danych wejściowych użytkownika.
Oznacza to ogromną alokację pamięci, co staje się dużym wykorzystaniem systemu plików wymiany, który symuluje sekcje dysku twardego, aby zachowywały się jak pamięć RAM, ale dysk twardy jest bardzo wolny.
Opcja StringBuilder wygląda dobrze dla tych, którzy używają systemu jako pojedynczy użytkownik, ale jeśli masz dwóch lub więcej użytkowników czytających duże pliki w tym samym czasie, masz problem.
źródło
To powinno wystarczyć, aby zacząć.
źródło
Spójrz na następujący fragment kodu. Wspomniałeś
Most files will be 30-40 MB
. To twierdzi, że odczytuje 180 MB w 1,4 sekundy na Intel Quad Core:Oryginalny artykuł
źródło
Lepiej byłoby skorzystać z obsługi plików mapowanych w pamięci tutaj .. Obsługa plików mapowanych w pamięci będzie dostępna w .NET 4 (myślę ... słyszałem, że ktoś o tym mówi), stąd ten wrapper, który używa p / wywołuje tę samą pracę.
Edycja: Zobacz tutaj w MSDN, jak to działa, oto wpis na blogu wskazujący, jak to się robi w nadchodzącej .NET 4, gdy pojawi się jako wydanie. Link, który podałem wcześniej, jest opakowaniem wokół pinvoke, aby to osiągnąć. Możesz zmapować cały plik do pamięci i przeglądać go jak przesuwne okno podczas przewijania pliku.
źródło
Wszystkie doskonałe odpowiedzi! Jednak dla kogoś, kto szuka odpowiedzi, wydaje się, że są one nieco niekompletne.
Standardowo łańcuch może mieć tylko rozmiar X, od 2 Gb do 4 Gb, w zależności od konfiguracji, te odpowiedzi tak naprawdę nie odpowiadają na pytanie OP. Jedną z metod jest praca z listą ciągów:
Niektórzy mogą chcieć tokenizować i dzielić linię podczas przetwarzania. Lista ciągów może teraz zawierać bardzo duże ilości tekstu.
źródło
Iterator może być idealny do tego typu pracy:
Możesz to nazwać za pomocą:
Po załadowaniu pliku iterator zwróci numer postępu od 0 do 100, którego możesz użyć do zaktualizowania paska postępu. Po zakończeniu pętli StringBuilder będzie zawierał zawartość pliku tekstowego.
Ponadto, ponieważ potrzebujesz tekstu, możemy po prostu użyć BinaryReader do czytania znakami, co zapewni prawidłowe wyrównanie buforów podczas odczytu dowolnych znaków wielobajtowych ( UTF-8 , UTF-16 itp.).
Wszystko to odbywa się bez korzystania z zadań w tle, wątków lub złożonych niestandardowych maszyn stanu.
źródło
Mój plik ma ponad 13 GB:
Poniższy link zawiera kod, który z łatwością odczytuje fragment pliku:
Przeczytaj duży plik tekstowy
Więcej informacji
źródło