在C++中逐行读取文件

Read file line by line using ifstream in C++

file.txt的内容包括:

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

其中,5 3是一个坐标对。如何在C++中逐行处理此数据?

我可以得到第一行,但如何得到文件的下一行?

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

首先,制作一个ifstream

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

两种标准方法是:

  • 假设每行由两个数字组成,并逐个标记地读取:

    1
    2
    3
    4
    5
    int a, b;
    while (infile >> a >> b)
    {
        // process pair (a,b)
    }
  • 基于行的分析,使用字符串流:

    1
    2
    3
    4
    5
    6
    7
    8
    9
    10
    11
    12
    #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)
    }
  • 您不应该混合(1)和(2),因为基于令牌的解析不会吞掉新行,因此,如果在基于令牌的提取使您到达行的末尾之后使用getline(),则可能会以虚假的空行结束。


    使用ifstream从文件中读取数据:

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

    如果您确实需要逐行阅读,请执行以下操作:

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

    但您可能只需要提取坐标对:

    1
    2
    int x, y;
    input >> x >> y;

    更新:

    在代码中使用ofstream myfile;,但是ofstream中的o代表output。如果要读取文件(输入),请使用ifstream。如果您既想读又想写,请使用fstream


    在C++中逐行读取文件可以以不同的方式完成。

    [fast]使用std::getline()循环

    最简单的方法是使用std::getline()调用打开std::ifstream并循环。代码清晰易懂。

    1
    2
    3
    4
    5
    6
    7
    8
    9
    10
    11
    #include <fstream>

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

    [快速]使用Boost的文件描述源

    另一种可能是使用Boost库,但是代码会变得更加冗长。性能与上面的代码非常相似(使用std::getline()循环)。

    1
    2
    3
    4
    5
    6
    7
    8
    9
    10
    11
    12
    13
    14
    15
    16
    17
    18
    19
    20
    21
    #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();
            }
        }
    }

    [最快]使用C代码

    如果性能对软件至关重要,您可以考虑使用C语言。此代码可以比上面的C++版本快4-5倍,参见下面的基准

    1
    2
    3
    4
    5
    6
    7
    8
    9
    10
    11
    12
    13
    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);

    基准——哪个更快?

    我用上面的代码做了一些性能基准测试,结果很有趣。我用包含100000行、1000000行和10000000行文本的ASCII文件测试了代码。每行文本平均包含10个单词。该程序是用-O3优化编译的,其输出被转发到/dev/null以从测量中删除测井时间变量。最后,但并非最不重要的是,每段代码都用printf()函数记录每一行,以确保一致性。

    结果显示每段代码读取文件所用的时间(毫秒)。

    两种C++方法之间的性能差异很小,在实践中不应该有任何差别。C代码的性能使基准测试令人印象深刻,并且在速度方面可以改变游戏规则。

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

    enter image description here


    既然坐标是成对的,为什么不为它们编写一个结构呢?

    1
    2
    3
    4
    5
    struct CoordinatePair
    {
        int x;
        int y;
    };

    然后可以为IStream编写重载的提取运算符:

    1
    2
    3
    4
    5
    6
    std::istream& operator>>(std::istream& is, CoordinatePair& coordinates)
    {
        is >> coordinates.x >> coordinates.y;

        return is;
    }

    然后你可以把一个坐标文件直接读取到一个向量中,就像这样:

    1
    2
    3
    4
    5
    6
    7
    8
    9
    10
    11
    12
    13
    14
    15
    16
    17
    18
    19
    20
    #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
    "
    ;
        }
        // Now you can work with the contents of v
    }


    如果输入为:

    1
    2
    3
    1,NYC
    2,ABQ
    ...

    您仍然可以应用相同的逻辑,如:

    1
    2
    3
    4
    5
    6
    7
    8
    9
    10
    11
    12
    #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 <<"
    "
    ;
    }
    infile.close();

    这是将数据加载到C++程序中的通用解决方案,并使用RealLoad函数。这可以为csv文件修改,但分隔符是一个空格。

    1
    2
    3
    4
    5
    6
    7
    8
    9
    10
    11
    12
    13
    14
    15
    16
    17
    18
    19
    20
    21
    22
    23
    24
    25
    26
    27
    28
    29
    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
    }


    此答案适用于Visual Studio 2017,如果您想从文本文件中读取与编译的控制台应用程序相关的位置。

    首先将文本文件(本例中为test.txt)放入解决方案文件夹。编译后,将文本文件与applicationname.exe保存在同一文件夹中

    C:users"username"source
    epos"solutionname""solutionname"

    1
    2
    3
    4
    5
    6
    7
    8
    9
    10
    11
    12
    13
    14
    15
    16
    17
    18
    19
    20
    21
    22
    23
    #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
    2
    3
    4
    5
    6
    7
    8
    9
        ifstream infile(szFilePath);

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

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