在C ++中使用ifstream逐行读取文件


612

file.txt的内容是:

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

哪里 5 3坐标对。如何在C ++中逐行处理此数据?

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

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

Answers:


916

首先,做一个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()在基于令牌的提取将您带到末尾之后使用,可能会导致虚假的空行行了。


1
@EdwardKarak:我不明白“逗号作为令牌”的含义。逗号不代表整数。
Kerrek SB 2014年

8
OP使用空格分隔两个整数。我想知道,如果OP使用a作为逗号分隔符,则while(infile >> a >> b)是否可以工作,因为这是我自己的程序中的场景
Edward Karak 2014年

30
@EdwardKarak:嗯,所以当您说“令牌”时,您的意思是“定界符”。对。使用逗号,您会说:int a, b; char c; while ((infile >> a >> c >> b) && (c == ','))
Kerrek SB 2014年

11
@KerrekSB:呵呵。我错了。我不知道它能做到这一点。我可能有一些自己的代码要重写。
Mark H

4
有关while(getline(f, line)) { }结构和错误处理的说明,请查看以下这篇文章: (gehrcke.de/2011/06/…(我认为我不需要为此而烦恼,甚至可以略微提前一些)日期这个答案)。
Jan-Philip Gehrcke博士,2015年

175

用于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;,但是oin ofstream代表output。如果要从文件(输入)中读取,请使用ifstream。如果您想同时读写使用fstream


8
您的解决方案有所改进:文件读取后,您的行变量不可见,这与Kerrek SB的第二种解决方案(又好又简单的解决方案)相反。
DanielTuzes 2013年

3
getlinestring see中,所以请不要忘记#include <string>
mxmlnkn

55

可以通过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

在此处输入图片说明


1
如果在控制台输出上删除C ++与C的同步,会发生什么?您可能正在衡量std::coutvs 的默认行为的已知缺点printf
user4581301

2
感谢您提出这个问题。我已重做测试,但性能仍然相同。我已编辑代码以printf()在所有情况下均使用该函数以保持一致性。我也尝试过std::cout在所有情况下使用,但这绝对没有区别。正如我在文本中所描述的,程序的输出转到,/dev/null因此不测量打印线条的时间。
HugoTeixeira

6
时髦。谢谢。想知道减速在哪里。
user4581301

4
嗨@HugoTeixeira,我知道这是一个旧线程,我试图复制您的结果,但看不到c和c ++之间的任何显着区别 github.com/simonsso/readfile_benchmarks
Simson

默认情况下,C ++输入输出流与同步cstdio。您应该尝试设置std::ios_base::sync_with_stdio(false)。我想您会获得更好的性能(尽管由于同步关闭时它是实现定义的,所以不能保证)。
Fareanor

11

由于您的坐标是成对在一起的,为什么不为它们编写一个结构?

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
}

1
当无法int从流中读取两个令牌时会发生什么operator>>?如何使它与回溯解析器一起使用(即,当operator>>失败时,将流回滚到以前的位置,最后返回false或类似的东西)?
fferri '16

如果不可能读取两个int令牌,则is流将求值为,false并且读取循环将在该点终止。您可以operator>>通过检查单个读数的返回值来检测到这一点。如果要回滚流,请致电is.clear()
Martin Broadhurst,2017年

用,operator>>这是更正确的说法,is >> std::ws >> coordinates.x >> std::ws >> coordinates.y >> std::ws;因为否则您将假设您的输入流处于空白跳过模式。
Darko Veberic

7

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

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

2

尽管不需要手动关闭文件,但是如果文件变量的范围较大,最好这样做:

    ifstream infile(szFilePath);

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

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

不确定这应否定表决。OP要求一种获取每一行的方法。这个答案可以做到,并为确保文件关闭提供了一个很好的提示。对于简单的程序,可能不需要,但至少要养成一个良好的习惯。可以通过添加几行代码来处理它拉出的各个行来改进它,但是总体而言,这是对OP问题的最简单答案。
Xandor

2

该答案适用于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();
}

1

这是将数据加载到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
}
By using our site, you acknowledge that you have read and understand our Cookie Policy and Privacy Policy.
Licensed under cc by-sa 3.0 with attribution required.