Jak wczytać plik do std::string
, tj. Czytać cały plik naraz?
Wzywający powinien określić tryb tekstowy lub binarny. Rozwiązanie powinno być zgodne z normami, przenośne i wydajne. Nie powinien niepotrzebnie kopiować danych ciągu i powinien unikać ponownego przydziału pamięci podczas odczytu ciągu.
Jednym ze sposobów na zrobienie tego może być statystyka rozmiaru pliku, zmiana rozmiaru std::string
i fread()
na edycję std::string
's const_cast<char*>()
' data()
. Wymaga std::string
to ciągłości danych, co nie jest wymagane przez standard, ale wydaje się, że tak jest w przypadku wszystkich znanych implementacji. Co gorsza, jeśli plik jest czytany w trybie tekstowym, std::string
rozmiar może nie odpowiadać rozmiarowi pliku.
W pełni poprawne, zgodne ze standardami i przenośne rozwiązania mogą być konstruowane przy użyciu std::ifstream
plików rdbuf()
a, std::ostringstream
a stamtąd do a std::string
. Może to jednak spowodować skopiowanie danych ciągu i / lub niepotrzebną zmianę alokacji pamięci.
- Czy wszystkie istotne implementacje bibliotek standardowych są wystarczająco inteligentne, aby uniknąć wszystkich niepotrzebnych kosztów ogólnych?
- Czy jest inny sposób, aby to zrobić?
- Czy przegapiłem jakąś ukrytą funkcję Boost, która już zapewnia pożądaną funkcjonalność?
void slurp(std::string& data, bool is_binary)
rdbuf
(ten w zaakceptowanej odpowiedzi) nie jest najszybszyread
.Odpowiedzi:
Jednym ze sposobów jest opróżnienie buforu strumienia do osobnego strumienia pamięci, a następnie przekonwertowanie go na
std::string
:To ładnie zwięzłe. Jednak, jak zauważono w pytaniu, powoduje to utworzenie zbędnej kopii i niestety zasadniczo nie ma możliwości usunięcia tej kopii.
Jedynym prawdziwym rozwiązaniem, które pozwala uniknąć zbędnych kopii, jest niestety ręczne wykonanie odczytu w pętli. Ponieważ C ++ ma teraz gwarantowane ciągłe ciągi, można napisać (≥ C ++ 14):
źródło
string
. To znaczy wymagające dwa razy więcej pamięci niż niektóre inne opcje. (Nie ma możliwości przeniesienia bufora). W przypadku dużego pliku oznaczałoby to znaczną karę, być może nawet powodując błąd alokacji.Zobacz tę odpowiedź na podobne pytanie.
Dla Twojej wygody ponownie publikuję rozwiązanie CTT:
To rozwiązanie zaowocowało około 20% szybszym czasem wykonania niż inne przedstawione tutaj odpowiedzi, biorąc pod uwagę średnią ze 100 uruchomień z tekstem Moby Dicka (1,3 mln). Nieźle jak na przenośne rozwiązanie C ++, chciałbym zobaczyć wyniki mmapowania pliku;)
źródło
ifs.seekg(0, ios::end)
wcześniej dzwonićtellg
? zaraz po otwarciu pliku wskaźnik odczytu znajduje się na początku itellg
zwraca zeronullptr
przez&bytes[0]
ios::ate
, więc myślę, że wersja z wyraźnym przesunięciem do końca byłaby bardziej czytelnaNajkrótszy wariant: Live On Coliru
Wymaga nagłówka
<iterator>
.Pojawiły się doniesienia, że ta metoda jest wolniejsza niż wstępne przydzielanie ciągu i używanie
std::istream::read
. Jednak w przypadku współczesnego kompilatora z włączoną optymalizacją wydaje się, że nie ma to już miejsca, chociaż względna wydajność różnych metod wydaje się być silnie zależna od kompilatora.źródło
Posługiwać się
lub coś bardzo bliskiego. Nie mam otwartego odwołania do biblioteki standardowej, aby sprawdzić się dwukrotnie.
Tak, rozumiem, że nie napisałem
slurp
funkcji zgodnie z pytaniem.źródło
operator>>
wczytywania do astd::basic_streambuf
zużywa (to, co zostało) strumień wejściowy, więc pętla jest niepotrzebna.Jeśli masz C ++ 17 (std :: filesystem), jest też ten sposób (który pobiera rozmiar pliku
std::filesystem::file_size
zamiastseekg
itellg
):Uwaga : może być konieczne użycie,
<experimental/filesystem>
astd::experimental::filesystem
Twoja standardowa biblioteka nie obsługuje jeszcze w pełni C ++ 17. Może być również konieczne zastąpienieresult.data()
przez,&result[0]
jeśli nie obsługuje danych innych niż stałe std :: basic_string .źródło
boost::filesystem
tak, że możesz również użyć boost, jeśli nie masz c ++ 17Nie mam wystarczającej reputacji, aby bezpośrednio komentować odpowiedzi za pomocą
tellg()
.Pamiętaj o tym
tellg()
w przypadku błędu może zwrócić -1. Jeśli przekazujesz wyniktellg()
jako parametr alokacji, powinieneś najpierw sprawdzić wynik.Przykład problemu:
W powyższym przykładzie, jeśli
tellg()
napotka błąd, zwróci -1. Niejawne rzutowanie między znakiem ze znakiem (tj. Wynikiemtellg()
) i bez znaku (tj. Argumentem dovector<char>
konstruktora) spowoduje, że Twój wektor błędnie przydzieli bardzo dużą liczbę bajtów. (Prawdopodobnie 4294967295 bajtów lub 4 GB.)Modyfikacja odpowiedzi paxos1977 w celu uwzględnienia powyższego:
źródło
To rozwiązanie dodaje sprawdzanie błędów do metody opartej na rdbuf ().
Dodaję tę odpowiedź, ponieważ dodanie sprawdzania błędów do oryginalnej metody nie jest tak trywialne, jak można by się spodziewać. Oryginalna metoda używa operatora wstawiania stringstream (
str_stream << file_stream.rdbuf()
). Problem polega na tym, że ustawia to bit failstream łańcucha, gdy nie są wstawiane żadne znaki. Może to być spowodowane błędem lub pustym plikiem. Jeśli sprawdzisz błędy, sprawdzając bit błędów, napotkasz fałszywy alarm podczas odczytu pustego pliku. Jak rozróżnić uzasadniony brak wstawienia jakichkolwiek znaków i „niepowodzenie” wstawienia jakichkolwiek znaków, ponieważ plik jest pusty?Możesz pomyśleć o jawnym sprawdzeniu pustego pliku, ale to więcej kodu i związanego z nim sprawdzania błędów.
Sprawdzanie stanu awarii
str_stream.fail() && !str_stream.eof()
nie działa, ponieważ operacja wstawiania nie ustawia eofbita (w strumieniu ostringstream ani w strumieniu ifstream).Tak więc rozwiązaniem jest zmiana operacji. Zamiast używać operatora wstawiania ostringstream (<<), użyj operatora ekstrakcji ifstream (>>), który ustawia eofbit. Następnie sprawdź stan awarii
file_stream.fail() && !file_stream.eof()
.Co ważne, gdy
file_stream >> str_stream.rdbuf()
napotka uzasadnioną awarię, nie powinien nigdy ustawiać eofbit (zgodnie z moim rozumieniem specyfikacji). Oznacza to, że powyższe sprawdzenie jest wystarczające do wykrycia uzasadnionych awarii.źródło
Coś takiego nie powinno być takie złe:
Zaletą jest to, że najpierw robimy rezerwę, więc nie będziemy musieli powiększać łańcucha podczas czytania. Wadą jest to, że robimy to char po znaku. Bardziej inteligentna wersja mogłaby pobrać całą odczytaną wartość bufora, a następnie wywołać niedomiar.
źródło
Oto wersja korzystająca z nowej biblioteki systemu plików z dość solidnym sprawdzaniem błędów:
źródło
infile.open
można też zaakceptowaćstd::string
bez konwersji z.c_str()
filepath
nie jeststd::string
, to jeststd::filesystem::path
. Okazuje się, żestd::ifstream::open
może zaakceptować również jedną z nich.std::filesystem::path
jest niejawnie zamieniany nastd::string
::open
funkcja członkowska,std::ifstream
która akceptuje,std::filesystem::path
działa tak, jakby::c_str()
metoda została wywołana na ścieżce. Podstawą::value_type
ścieżek jestchar
POSIX.Możesz użyć funkcji „std :: getline” i określić „eof” jako separator. Wynikowy kod jest jednak nieco niejasny:
źródło
Nigdy nie zapisuj w buforze const char * std :: string. Nigdy przenigdy! Takie postępowanie jest ogromnym błędem.
Zarezerwuj () miejsce na cały ciąg w swoim std :: string, wczytaj fragmenty pliku o rozsądnym rozmiarze do bufora i dołącz () go. Jak duże muszą być fragmenty, zależy od rozmiaru pliku wejściowego. Jestem prawie pewien, że wszystkie inne przenośne i zgodne z STL mechanizmy zrobią to samo (ale mogą wyglądać ładniej).
źródło
std::string
bufora będzie OK ; i uważam, że działało poprawnie na wszystkich wcześniejszych wdrożeniachstd::string::data()
metodę nie będącą stałą modyfikacją bufora łańcuchowego bezpośrednio, bez uciekania się do takich sztuczek jak&str[0]
.stosowanie:
źródło
Zaktualizowana funkcja oparta na rozwiązaniu CTT:
Istnieją dwie ważne różnice:
tellg()
nie gwarantuje zwrócenia przesunięcia w bajtach od początku pliku. Zamiast tego, jak wskazał Puzomor Croatia, jest to raczej token, którego można używać w wywołaniach fstream.gcount()
jednak nie zwraca ilość niesformatowanych bajtów ostatni wyodrębnione. Dlatego otwieramy plik, wyodrębniamy i odrzucamy całą jego zawartość za pomocą,ignore()
aby uzyskać rozmiar pliku, i na tej podstawie konstruujemy ciąg wyjściowy.Po drugie, unikamy konieczności kopiowania danych pliku z a
std::vector<char>
do astd::string
, zapisując bezpośrednio do ciągu.Pod względem wydajności powinno to być absolutnie najszybsze, przydzielając ciąg o odpowiednim rozmiarze z wyprzedzeniem i wywołując
read()
raz. Ciekawostką jest to, że użycieignore()
icountg()
zamiastate
itellg()
na gcc kompiluje się do prawie tego samego , krok po kroku.źródło
ifs.seekg(0)
zamiastifs.clear()
(wtedy to działa).źródło