Baca file baris demi baris menggunakan ifstream di C ++


612

Isi file.txt adalah:

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

Di mana 5 3pasangan koordinat. Bagaimana cara saya memproses data ini baris demi baris dalam C ++?

Saya bisa mendapatkan baris pertama, tetapi bagaimana cara mendapatkan baris file berikutnya?

ifstream myfile;
myfile.open ("text.txt");

Jawaban:


916

Pertama, buat ifstream:

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

Dua metode standar adalah:

  1. Asumsikan bahwa setiap baris terdiri dari dua angka dan bacalah token dengan token:

    int a, b;
    while (infile >> a >> b)
    {
        // process pair (a,b)
    }
  2. Penguraian berbasis garis, menggunakan aliran string:

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

Anda tidak boleh mencampur (1) dan (2), karena parsing berbasis token tidak melahap baris baru, jadi Anda mungkin berakhir dengan baris kosong palsu jika Anda menggunakan getline()setelah ekstraksi berbasis token membuat Anda sampai akhir baris sudah.


1
@ EdwardKarak: Saya tidak mengerti apa artinya "koma sebagai token". Koma tidak mewakili bilangan bulat.
Kerrek SB

8
OP menggunakan spasi untuk membatasi dua bilangan bulat. Saya ingin tahu apakah sementara (infile >> a >> b) akan berfungsi jika OP menggunakan koma sebagai pembatas, karena itu adalah skenario dalam program saya sendiri
Edward Karak

30
@ EdwardKarak: Ah, jadi ketika Anda mengatakan "token" Anda berarti "pembatas". Baik. Dengan koma, Anda akan mengatakan:int a, b; char c; while ((infile >> a >> c >> b) && (c == ','))
Kerrek SB

11
@ GerrekSB: Hah. Saya salah. Saya tidak tahu itu bisa melakukan itu. Saya mungkin punya beberapa kode sendiri untuk ditulis ulang.
Mark H

4
Untuk penjelasan tentang while(getline(f, line)) { }konstruk dan mengenai penanganan kesalahan, silakan lihat artikel (saya) ini: gehrcke.de/2011/06/… (saya pikir saya tidak perlu memiliki hati nurani yang buruk memposting ini di sini, bahkan sedikit sebelum tanggal jawaban ini).
Dr. Jan-Philip Gehrcke

175

Gunakan ifstreamuntuk membaca data dari file:

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

Jika Anda benar-benar perlu membaca baris demi baris, maka lakukan ini:

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

Tetapi Anda mungkin hanya perlu mengekstrak pasangan koordinat:

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

Memperbarui:

Dalam kode Anda yang Anda gunakan ofstream myfile;, namun odalam ofstreamsingkatan output. Jika Anda ingin membaca dari file (input) gunakan ifstream. Jika Anda ingin membaca dan menulis digunakan fstream.


8
Solusi Anda sedikit ditingkatkan: variabel baris Anda tidak terlihat setelah pembacaan file berbeda dengan solusi kedua Kerrek SB yang juga merupakan solusi yang baik dan sederhana.
DanielTuzes

3
getlineada di string lihat , jadi jangan lupa#include <string>
mxmlnkn

55

Membaca file baris demi baris dalam C ++ dapat dilakukan dengan beberapa cara berbeda.

[Cepat] Loop dengan std :: getline ()

Pendekatan paling sederhana adalah dengan membuka panggilan std :: ifstream dan loop menggunakan std :: getline (). Kode ini bersih dan mudah dimengerti.

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

[Cepat] Gunakan Boost's file_description_source

Kemungkinan lain adalah dengan menggunakan pustaka Boost, tetapi kode mendapat sedikit lebih banyak verbose. Kinerja sangat mirip dengan kode di atas (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();
        }
    }
}

[Tercepat] Gunakan kode C

Jika kinerja sangat penting untuk perangkat lunak Anda, Anda dapat mempertimbangkan menggunakan bahasa C. Kode ini bisa 4-5 kali lebih cepat daripada versi C ++ di atas, lihat patokan di bawah ini

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 - Mana yang lebih cepat?

Saya telah melakukan beberapa tolok ukur kinerja dengan kode di atas dan hasilnya menarik. Saya telah menguji kode dengan file ASCII yang berisi 100.000 baris, 1.000.000 baris dan 10.000.000 baris teks. Setiap baris teks rata-rata berisi 10 kata. Program ini dikompilasi dengan -O3optimasi dan outputnya diteruskan /dev/nulluntuk menghapus variabel waktu logging dari pengukuran. Terakhir, namun tidak kalah pentingnya, setiap potongan kode mencatat setiap baris dengan printf()fungsi untuk konsistensi.

Hasilnya menunjukkan waktu (dalam ms) yang diambil oleh setiap bagian kode untuk membaca file.

Perbedaan kinerja antara kedua pendekatan C ++ minimal dan seharusnya tidak membuat perbedaan dalam praktik. Performa kode C adalah apa yang membuat benchmark itu mengesankan dan bisa menjadi pengubah permainan dalam hal kecepatan.

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

masukkan deskripsi gambar di sini


1
Apa yang terjadi jika Anda menghapus sinkronisasi C ++ dengan C pada output konsol? Anda mungkin mengukur kerugian dikenal dari perilaku default std::coutvs printf.
user4581301

2
Terima kasih telah menyampaikan keprihatinan ini. Saya telah mengulang tes dan kinerjanya masih sama. Saya telah mengedit kode untuk menggunakan printf()fungsi dalam semua kasus untuk konsistensi. Saya juga telah mencoba menggunakan std::coutdalam semua kasus dan ini sama sekali tidak membuat perbedaan. Seperti yang baru saja saya jelaskan dalam teks, output dari program berjalan /dev/nullsehingga waktu untuk mencetak garis tidak diukur.
HugoTeixeira

6
Asyik. Terima kasih. Bertanya-tanya di mana pelambatannya.
user4581301

4
Hai @HugoTeixeira Saya tahu ini adalah utas lama, saya mencoba mereplikasi hasil Anda dan tidak dapat melihat perbedaan yang signifikan antara c dan c ++ github.com/simonsso/readfile_benchmarks
Simson

Secara default, aliran keluar-masuk C ++ disinkronkan dengan cstdio. Anda harus mencoba dengan pengaturan std::ios_base::sync_with_stdio(false). Saya kira Anda akan memperoleh kinerja jauh lebih baik (Ini tidak dijamin karena implementasi-didefinisikan ketika sinkronisasi dimatikan).
Fareanor

11

Karena koordinat Anda dimiliki bersama sebagai pasangan, mengapa tidak menulis struct untuknya?

struct CoordinatePair
{
    int x;
    int y;
};

Kemudian Anda dapat menulis operator ekstraksi yang kelebihan beban untuk istreams:

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

    return is;
}

Dan kemudian Anda dapat membaca file koordinat langsung ke vektor seperti ini:

#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
}

1
Apa yang terjadi ketika tidak memungkinkan untuk membaca dua inttoken dari aliran operator>>? Bagaimana cara membuatnya bekerja dengan parser backtracking (yaitu ketika operator>>gagal, memutar kembali aliran ke posisi sebelumnya dan kembali palsu atau sesuatu seperti itu)?
fferri

Jika tidak mungkin membaca dua inttoken, maka isaliran akan mengevaluasi falsedan loop pembacaan akan berakhir pada titik itu. Anda dapat mendeteksi ini di dalam operator>>dengan memeriksa nilai balik dari masing-masing bacaan. Jika Anda ingin memutar kembali aliran, Anda akan menelepon is.clear().
Martin Broadhurst

di operator>>dalamnya lebih tepat untuk mengatakan is >> std::ws >> coordinates.x >> std::ws >> coordinates.y >> std::ws;karena jika tidak Anda mengasumsikan bahwa aliran input Anda dalam mode skipping-spasi.
Darko Veberic

7

Memperluas jawaban yang diterima, jika inputnya adalah:

1,NYC
2,ABQ
...

Anda masih dapat menerapkan logika yang sama, seperti ini:

#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();

2

Meskipun tidak perlu menutup file secara manual, tetapi ada baiknya untuk melakukannya jika ruang lingkup variabel file lebih besar:

    ifstream infile(szFilePath);

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

    if(infile.is_open())
        infile.close();

Tidak yakin ini layak mendapatkan suara turun. OP meminta cara untuk mendapatkan setiap baris. Jawaban ini melakukan itu dan memberikan tip yang bagus untuk memastikan file ditutup. Untuk program sederhana mungkin tidak diperlukan, tetapi setidaknya kebiasaan yang HEBAT untuk terbentuk. Mungkin dapat ditingkatkan dengan menambahkan beberapa baris kode untuk memproses setiap baris yang ditarik tetapi secara keseluruhan adalah jawaban paling sederhana untuk pertanyaan OP.
Xandor

2

Jawaban ini untuk visual studio 2017 dan jika Anda ingin membaca dari file teks yang lokasinya relatif terhadap aplikasi konsol yang dikompilasi.

pertama-tama masukkan filefile Anda (test.txt dalam kasus ini) ke dalam folder solusi Anda. Setelah kompilasi, simpan file teks dalam folder yang sama dengan applicationName.exe

C: \ Users \ "username" \ source \ repos \ "solutionName" \ "solutionName"

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

1

Ini adalah solusi umum untuk memuat data ke dalam program C ++, dan menggunakan fungsi readline. Ini dapat dimodifikasi untuk file CSV, tetapi pembatasnya adalah ruang di sini.

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
}
Dengan menggunakan situs kami, Anda mengakui telah membaca dan memahami Kebijakan Cookie dan Kebijakan Privasi kami.
Licensed under cc by-sa 3.0 with attribution required.