Czytaj plik linia po linii używając ifstream w C ++

612

Zawartość pliku.txt to:

5 3
6 4
7 1
10 5
11 6
12 3
12 4

Gdzie 5 3jest para współrzędnych. Jak przetwarzać tę linię danych po linii w C ++?

Jestem w stanie uzyskać pierwszą linię, ale jak mogę uzyskać następną linię pliku?

ifstream myfile;
myfile.open ("text.txt");
cytrynowy
źródło

Odpowiedzi:

916

Najpierw wykonaj ifstream:

#include <fstream>
std::ifstream infile("thefile.txt");

Dwie standardowe metody to:

  1. Załóżmy, że każda linia składa się z dwóch liczb i odczytuje token po tokenie:

    int a, b;
    while (infile >> a >> b)
    {
        // process pair (a,b)
    }
  2. Analiza liniowa przy użyciu strumieni ciągów:

    #include <sstream>
    #include <string>
    
    std::string line;
    while (std::getline(infile, line))
    {
        std::istringstream iss(line);
        int a, b;
        if (!(iss >> a >> b)) { break; } // error
    
        // process pair (a,b)
    }

Nie powinieneś mieszać (1) i (2), ponieważ parsowanie oparte na tokenach nie pożera nowych linii, więc możesz skończyć z fałszywymi pustymi liniami, jeśli użyjesz getline()po wyodrębnieniu opartym na tokenach doszedł do końca linia już.

Kerrek SB
źródło
1
@EdwardKarak: Nie rozumiem, co oznacza „przecinek jako token”. Przecinki nie reprezentują liczb całkowitych.
Kerrek SB,
8
OP użył spacji do ograniczenia dwóch liczb całkowitych. Chciałem wiedzieć, czy while (infile >> a >> b) zadziałałoby, gdyby PO użył jako przecinka separatora, ponieważ taki jest scenariusz w moim własnym programie
Edward Karak
30
@EdwardKarak: Ach, więc kiedy powiedziałeś „token”, miałeś na myśli „delimiter”. Dobrze. int a, b; char c; while ((infile >> a >> c >> b) && (c == ','))
Przecinkiem
11
@KerrekSB: Huh. Myliłem się. Nie wiedziałem, że to może zrobić. Mogę mieć własny kod do przepisania.
Mark H
4
Aby uzyskać wyjaśnienie while(getline(f, line)) { }konstrukcji i dotyczące obsługi błędów, zapoznaj się z tym (moim) artykułem: gehrcke.de/2011/06/… (Myślę, że nie muszę mieć złego sumienia, zamieszczając to tutaj, nawet nieco wcześniej datuje tę odpowiedź).
Dr Jan-Philip Gehrcke
175

Służy ifstreamdo odczytu danych z pliku:

std::ifstream input( "filename.ext" );

Jeśli naprawdę potrzebujesz czytać wiersz po wierszu, wykonaj następujące czynności:

for( std::string line; getline( input, line ); )
{
    ...for each line in input...
}

Ale prawdopodobnie musisz po prostu wyodrębnić pary współrzędnych:

int x, y;
input >> x >> y;

Aktualizacja:

W swoim kodzie używasz ofstream myfile;, jednak oin ofstreamoznacza output. Jeśli chcesz czytać z pliku (wejście) użyj ifstream. Jeśli chcesz zarówno czytać, jak i pisać, użyj fstream.

K-ballo
źródło
8
Twoje rozwiązanie zostało nieco ulepszone: zmienna linii nie jest widoczna po wczytaniu pliku, w przeciwieństwie do drugiego rozwiązania Kerrek SB, które jest również dobrym i prostym rozwiązaniem.
DanielTuzes
3
getlinejest string widoczny , więc nie zapomnij o#include <string>
mxmlnkn
55

Czytanie pliku wiersz po wierszu w C ++ można wykonać na kilka różnych sposobów.

[Fast] Pętla ze std :: getline ()

Najprostszym podejściem jest otwarcie std :: ifstream i zapętlenie za pomocą wywołań std :: getline (). Kod jest czysty i łatwy do zrozumienia.

#include <fstream>

std::ifstream file(FILENAME);
if (file.is_open()) {
    std::string line;
    while (std::getline(file, line)) {
        // using printf() in all tests for consistency
        printf("%s", line.c_str());
    }
    file.close();
}

[Szybko] Użyj Boost's file_description_source

Inną możliwością jest użycie biblioteki Boost, ale kod staje się bardziej szczegółowy. Wydajność jest dość podobna do powyższego kodu (Loop with std :: getline ()).

#include <boost/iostreams/device/file_descriptor.hpp>
#include <boost/iostreams/stream.hpp>
#include <fcntl.h>

namespace io = boost::iostreams;

void readLineByLineBoost() {
    int fdr = open(FILENAME, O_RDONLY);
    if (fdr >= 0) {
        io::file_descriptor_source fdDevice(fdr, io::file_descriptor_flags::close_handle);
        io::stream <io::file_descriptor_source> in(fdDevice);
        if (fdDevice.is_open()) {
            std::string line;
            while (std::getline(in, line)) {
                // using printf() in all tests for consistency
                printf("%s", line.c_str());
            }
            fdDevice.close();
        }
    }
}

[Najszybszy] Użyj kodu C.

Jeśli wydajność ma kluczowe znaczenie dla twojego oprogramowania, możesz rozważyć użycie języka C. Ten kod może być 4-5 razy szybszy niż wersje C ++ powyżej, patrz test porównawczy poniżej

FILE* fp = fopen(FILENAME, "r");
if (fp == NULL)
    exit(EXIT_FAILURE);

char* line = NULL;
size_t len = 0;
while ((getline(&line, &len, fp)) != -1) {
    // using printf() in all tests for consistency
    printf("%s", line);
}
fclose(fp);
if (line)
    free(line);

Benchmark - Który jest szybszy?

Z powyższym kodem przeprowadziłem kilka testów wydajności, a wyniki są interesujące. Przetestowałem kod z plikami ASCII, które zawierają 100 000 wierszy, 1 000 000 wierszy i 10 000 000 wierszy tekstu. Każdy wiersz tekstu zawiera średnio 10 słów. Program jest kompilowany z -O3optymalizacją, a jego dane wyjściowe są przekazywane do /dev/nullw celu usunięcia zmiennej czasowej rejestrowania z pomiaru. Na koniec każdy fragment kodu rejestruje każdą linię za pomocą printf()funkcji zapewniającej spójność.

Wyniki pokazują czas (w ms), jaki każdy fragment kodu potrzebował na odczyt plików.

Różnica w wydajności między dwoma podejściami C ++ jest minimalna i nie powinna robić żadnej różnicy w praktyce. Wydajność kodu C sprawia, że ​​test porównawczy robi wrażenie i może być przełomem pod względem szybkości.

                             10K lines     100K lines     1000K lines
Loop with std::getline()         105ms          894ms          9773ms
Boost code                       106ms          968ms          9561ms
C code                            23ms          243ms          2397ms

wprowadź opis zdjęcia tutaj

HugoTeixeira
źródło
1
Co się stanie, jeśli usuniesz synchronizację C ++ z C na wyjściach konsoli? Można się pomiaru znaną wadę domyślnego zachowania std::coutvs printf.
user4581301
2
Dzięki za wniesienie tej troski. Powtórzyłem testy, a wydajność jest nadal taka sama. Zredagowałem kod, aby printf()we wszystkich przypadkach korzystać z funkcji w celu zachowania spójności. Próbowałem też używać std::coutwe wszystkich przypadkach i to absolutnie nie miało znaczenia. Jak właśnie opisałem w tekście, wyjście programu idzie do, /dev/nullwięc czas drukowania linii nie jest mierzony.
HugoTeixeira
6
Groovy. Dzięki. Zastanawiam się, gdzie jest spowolnienie.
user4581301
4
Cześć @ HugoTeixeira Wiem, że to stary wątek. Próbowałem powtórzyć wyniki i nie widziałem żadnej znaczącej różnicy między c i c ++ github.com/simonsso/readfile_benchmarks
Simson
Domyślnie strumienie wejściowe C ++ są synchronizowane cstdio. Powinieneś spróbować z ustawieniem std::ios_base::sync_with_stdio(false). Sądzę, że uzyskałbyś znacznie lepszą wydajność (nie jest to jednak gwarantowane, ponieważ jest zdefiniowane w implementacji, gdy synchronizacja jest wyłączona).
Fareanor,
11

Ponieważ twoje współrzędne należą do siebie jako pary, dlaczego nie napisać dla nich struktury?

struct CoordinatePair
{
    int x;
    int y;
};

Następnie możesz napisać przeciążony operator ekstrakcji dla istreams:

std::istream& operator>>(std::istream& is, CoordinatePair& coordinates)
{
    is >> coordinates.x >> coordinates.y;

    return is;
}

A potem możesz odczytać plik współrzędnych prosto do wektora takiego:

#include <fstream>
#include <iterator>
#include <vector>

int main()
{
    char filename[] = "coordinates.txt";
    std::vector<CoordinatePair> v;
    std::ifstream ifs(filename);
    if (ifs) {
        std::copy(std::istream_iterator<CoordinatePair>(ifs), 
                std::istream_iterator<CoordinatePair>(),
                std::back_inserter(v));
    }
    else {
        std::cerr << "Couldn't open " << filename << " for reading\n";
    }
    // Now you can work with the contents of v
}
Martin Broadhurst
źródło
1
Co się stanie, gdy nie będzie można odczytać dwóch inttokenów ze strumienia operator>>? W jaki sposób można sprawić, by działał z analizatorem składni cofania (tj. Gdy operator>>zawiedzie, przywróć strumień do poprzedniej pozycji, zwróć wartość false lub coś takiego)?
fferri
Jeśli nie można odczytać dwóch inttokenów, isstrumień oceni na, falsea pętla odczytu zakończy się w tym momencie. Możesz to wykryć operator>>, sprawdzając wartość zwrotną poszczególnych odczytów. Jeśli chcesz wycofać strumień, zadzwoń is.clear().
Martin Broadhurst
w tym operator>>bardziej poprawne jest powiedzenie, is >> std::ws >> coordinates.x >> std::ws >> coordinates.y >> std::ws;ponieważ w przeciwnym razie zakładasz, że twój strumień wejściowy jest w trybie pomijania białych znaków.
Darko Veberic
7

Rozwijanie przyjętej odpowiedzi, jeśli dane wejściowe to:

1,NYC
2,ABQ
...

nadal będziesz mógł zastosować tę samą logikę:

#include <fstream>

std::ifstream infile("thefile.txt");
if (infile.is_open()) {
    int number;
    std::string str;
    char c;
    while (infile >> number >> c >> str && c == ',')
        std::cout << number << " " << str << "\n";
}
infile.close();
gsamaras
źródło
2

Chociaż nie ma potrzeby ręcznego zamykania pliku, ale warto to zrobić, jeśli zakres zmiennej pliku jest większy:

    ifstream infile(szFilePath);

    for (string line = ""; getline(infile, line); )
    {
        //do something with the line
    }

    if(infile.is_open())
        infile.close();
Vijay Bansal
źródło
Nie jestem pewien, czy to zasługiwało na głos w dół. OP poprosił o sposób uzyskania każdej linii. Ta odpowiedź robi to i daje świetną wskazówkę, jak upewnić się, że plik zostanie zamknięty. W przypadku prostego programu może nie być potrzebny, ale należy formować WIELKI nawyk. Można to poprawić, dodając kilka wierszy kodu do przetwarzania pojedynczych linii, które pobiera, ale ogólnie jest to najprostsza odpowiedź na pytanie PO.
Xandor
2

Ta odpowiedź dotyczy programu Visual Studio 2017 i jeśli chcesz czytać z pliku tekstowego, która lokalizacja jest względna w stosunku do skompilowanej aplikacji konsoli.

najpierw umieść plik tekstowy (w tym przypadku test.txt) w folderze rozwiązania. Po skompilowaniu trzymaj plik tekstowy w tym samym folderze z applicationName.exe

C: \ Users \ "nazwa użytkownika" \ source \ repos \ "nazwa rozwiązania" \ "nazwa rozwiązania"

#include <iostream>
#include <fstream>

using namespace std;
int main()
{
    ifstream inFile;
    // open the file stream
    inFile.open(".\\test.txt");
    // check if opening a file failed
    if (inFile.fail()) {
        cerr << "Error opeing a file" << endl;
        inFile.close();
        exit(1);
    }
    string line;
    while (getline(inFile, line))
    {
        cout << line << endl;
    }
    // close the file stream
    inFile.close();
}
Universus
źródło
1

Jest to ogólne rozwiązanie do ładowania danych do programu C ++ i wykorzystuje funkcję readline. Można to zmodyfikować dla plików CSV, ale separator jest tutaj spacją.

int n = 5, p = 2;

int X[n][p];

ifstream myfile;

myfile.open("data.txt");

string line;
string temp = "";
int a = 0; // row index 

while (getline(myfile, line)) { //while there is a line
     int b = 0; // column index
     for (int i = 0; i < line.size(); i++) { // for each character in rowstring
          if (!isblank(line[i])) { // if it is not blank, do this
              string d(1, line[i]); // convert character to string
              temp.append(d); // append the two strings
        } else {
              X[a][b] = stod(temp);  // convert string to double
              temp = ""; // reset the capture
              b++; // increment b cause we have a new number
        }
    }

  X[a][b] = stod(temp);
  temp = "";
  a++; // onto next row
}
mjr2000
źródło