【问题标题】:Read file line by line using ifstream in C++在 C++ 中使用 ifstream 逐行读取文件
【发布时间】:2011-12-13 17:06:21
【问题描述】:

file.txt的内容是:

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

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

我可以获取第一行,但是如何获取文件的下一行?

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

【问题讨论】:

    标签: c++ file-io ofstream


    【解决方案1】:

    首先,创建一个ifstream

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

    两种标准方法是:

    1. 假设每一行由两个数字组成,逐个令牌读取:

      int a, b;
      while (infile >> a >> b)
      {
          // process pair (a,b)
      }
      
    2. 基于行的解析,使用字符串流:

      #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(),您最终可能会得到虚假的空行你已经到了一行的末尾。

    【讨论】:

    • @EdwardKarak:我不明白“逗号作为标记”是什么意思。逗号不代表整数。
    • OP 使用空格来分隔两个整数。我想知道如果 OP 使用 a 作为逗号分隔符,while (infile >> a >> b) 是否会起作用,因为这是我自己程序中的场景
    • @EdwardKarak:啊,所以当您说“令牌”时,您的意思是“分隔符”。正确的。用逗号,你会说:int a, b; char c; while ((infile &gt;&gt; a &gt;&gt; c &gt;&gt; b) &amp;&amp; (c == ','))
    • @KerrekSB:嗯。我错了。我不知道它可以做到这一点。我可能有一些自己的代码要重写。
    • 有关while(getline(f, line)) { } 构造和错误处理的解释,请查看这篇(我的)文章:gehrcke.de/2011/06/…(我认为我不需要良心不好在这里发布此内容,它甚至稍微早于这个答案)。
    【解决方案2】:

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

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

    如果你真的需要逐行阅读,那么这样做:

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

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

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

    更新:

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

    【讨论】:

    • 您的解决方案有所改进:与 Kerrek SB 的第二个解决方案相比,您的 line 变量在文件读入后不可见,这也是一个很好且简单的解决方案。
    • getlinestring see,所以不要忘记#include &lt;string&gt;
    【解决方案3】:

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

    [快速] 使用 std::getline() 循环

    最简单的方法是打开一个 std::ifstream 并使用 std::getline() 调用进行循环。代码简洁易懂。

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

    [快速] 使用 Boost 的 file_description_source

    另一种可能性是使用 Boost 库,但代码会变得更加冗长。性能与上面的代码非常相似(使用 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();
            }
        }
    }
    

    【最快】使用C代码

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

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

    基准测试 -- 哪个更快?

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

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

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

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

    【讨论】:

    • 如果在控制台输出上删除 C++ 与 C 的同步会发生什么?您可能正在衡量 std::coutprintf 的默认行为的已知缺点。
    • 感谢您提出此问题。我重做了测试,性能还是一样。我已编辑代码以在所有情况下都使用 printf() 函数以保持一致性。我也尝试在所有情况下使用std::cout,这完全没有区别。正如我刚刚在正文中描述的那样,程序的输出会转到/dev/null,因此不会测量打印行的时间。
    • 时髦。谢谢。想知道减速在哪里。
    • 嗨@HugoTeixeira我知道这是一个旧线程,我试图复制你的结果,但看不出c和c++之间有任何显着差异github.com/simonsso/readfile_benchmarks
    • @Fareanor 这不正确。它只影响 standard C++ 流,std::ifstream file 不是其中之一。 en.cppreference.com/w/cpp/io/ios_base/sync_with_stdio
    【解决方案4】:

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

    struct CoordinatePair
    {
        int x;
        int y;
    };
    

    然后你可以为 istream 编写一个重载的提取操作符:

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

    然后你可以像这样直接将坐标文件读入向量:

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

    【讨论】:

    • 当无法从operator&gt;&gt; 的流中读取两个int 令牌时会发生什么?如何使它与回溯解析器一起工作(即当operator&gt;&gt; 失败时,将流回滚到先前的位置 end return false 或类似的东西)?
    • 如果无法读取两个int 令牌,则is 流将评估为false,并且读取循环将在该点终止。您可以通过检查各个读取的返回值在operator&gt;&gt; 中检测到这一点。如果你想回滚流,你可以调用is.clear()
    • operator&gt;&gt; 中说is &gt;&gt; std::ws &gt;&gt; coordinates.x &gt;&gt; std::ws &gt;&gt; coordinates.y &gt;&gt; std::ws; 更正确,否则您会假设您的输入流处于空白跳过模式。
    【解决方案5】:

    扩展接受的答案,如果输入是:

    1,NYC
    2,ABQ
    ...
    

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

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

    【讨论】:

      【解决方案6】:

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

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

      C:\Users\"用户名"\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();
      }
      

      【讨论】:

        【解决方案7】:

        虽然不需要手动关闭文件,但如果文件变量的范围更大,这样做是个好主意:

            ifstream infile(szFilePath);
        
            for (string line = ""; getline(infile, line); )
            {
                //do something with the line
            }
        
            if(infile.is_open())
                infile.close();
        

        【讨论】:

        • 不确定这值得否决。 OP 要求提供一种获取每一行的方法。这个答案可以做到这一点,并提供了确保文件关闭的一个很好的提示。对于一个简单的程序,可能不需要它,但至少要养成一个很好的习惯。可以通过添加几行代码来处理它提取的各个行来改进它,但总的来说是对 OPs 问题的最简单的答案。
        【解决方案8】:

        这是将数据加载到 C++ 程序中的通用解决方案,并使用 readline 函数。这可以针对 CSV 文件进行修改,但这里的分隔符是一个空格。

        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
        }
        

        【讨论】:

          猜你喜欢
          • 1970-01-01
          • 2022-01-25
          • 1970-01-01
          • 2010-12-24
          • 2010-11-19
          相关资源
          最近更新 更多