3

我正在尝试在循环中逐行读取带有 std::ifstream 的文件。在同一个循环中,我试图找到两个标签,它们包含一个我想作为一个整体阅读的块。

我想,我可以用 seekg 跟踪块的开始和结束位置,并在找到两个位置后使用 read(*,end-start) 读取块。

但是,tellg 返回流位置,但由于文件已在文本模式下打开 [以便我能够调用 getline] 并使用 \r\n 作为行尾,ifstream 的参数“字符数”- read-method 是指从 \r\n 转换为 \n 后的数字,因此我读取的字符比我预期的要多 n 个字符,其中 n 是两个标签之间的行数。

显然,有很多解决方法,但我正在寻找一个很好且直观的解决方案。有什么建议么?

EDIT1@130507:我的目标是继续使用 std lib 流,优先考虑内存而不是速度,我需要解析和处理这两个部分,即周围部分和标签之间的块。

我希望有一些可用的东西,比如在已经打开的文本模式流中切换到二进制模式,或者有一些(基类)raw-read-method 不能像 read 那样进行字符转换,或者一些映射器方法允许在字符翻译之前和之后流 indizes 之间的映射,但到目前为止找不到任何东西。

这是一些代码:

std::ifstream testDataFileStream;
testDataFileStream.open(testDataFileName, std::ios_base::in);
testDataFileStream.unsetf(std::ios::skipws); // No white space skipping
if (testDataFileStream) {
    std::string line;
    while (getline(testDataFileStream, line))
        if (line == "starttag")
            break;
    if (line == "starttag")
    {
        std::ifstream::pos_type cmdStartPos = testDataFileStream.tellg();
        std::ifstream::pos_type cmdEndPos;
        while (getline(testDataFileStream, line))
            if (line == "endtag")
                break;
            else
                cmdEndPos = testDataFileStream.tellg();
        if (line == "endtag")
        {
            std::streamsize nofBytesToRead = cmdEndPos - cmdStartPos;

            // now, one possible workaround follows, however, it's obviously quite lame
            testDataFileStream.close();
            testDataFileStream.open(testDataFileName, std::ios_base::in | std::ios::binary);
            testDataFileStream.seekg(cmdStartPos);
            std::string cmdsString;
            cmdsString.resize(nofBytesToRead+1);
            testDataFileStream.read(&cmdsString[0], nofBytesToRead);
        } else {}
    } else {}
    testDataFileStream.close();
} else {}

测试文件可能如下所示:

some text
more text
starttag
much stuff on many lines
endtag
even more text
4

4 回答 4

1

为了扩展 Jerry Coffin 的方法,这里有一个简单的例子。通过使用 C++11 的std::move额外分配可以避免。但是请注意,这getline()将导致其std::string参数的重复重新分配,尤其是对于长行。如果您真的关心内存管理,您应该考虑将您的数据读入固定大小的缓冲区。

无论如何,这是代码:

#include <fstream>
#include <iostream>
#include <vector>
#include <utility>

int main() {
    std::ifstream testDataFileStream;
    testDataFileStream.open("data.txt", std::ios_base::in);
    testDataFileStream.unsetf(std::ios::skipws); // No white space skipping
    if (testDataFileStream) {
        std::vector<std::string> buffer;
        std::string line;
        bool found = false;
        while (getline(testDataFileStream, line)) {
            if (line == "starttag")
                found = true;
            if (found) {
                buffer.push_back(std::move(line));
                if (line == "endtag")
                    found = false;
            }
        }
        for (std::string & s : buffer) {
            std::cout << s << std::endl;
        }
    }
}
于 2013-05-18T21:17:28.537 回答
1

以文本模式打开文件时会发生字符翻译。

您可以以二进制模式打开文件。ios::binary

于 2013-05-16T16:51:14.023 回答
1

正如 Jerry Coffin 和 Terenty Rezman 所建议的那样,这种tellg()/seekg()方法会让你陷入困境。由于您希望解析所有行并对starttag/endtag块进行一些特定的解析,我建议您:-

  • 以文本模式逐行读取文件
  • 跟踪您何时进入和离开这些街区
  • 当您阅读其中的行时,以某种适当的方式“组装”一个块。
  • 对每个单独的块内和块外行做任何正确的事情
  • 每当你完成一个块时,做任何对的事情。
  • 并随时处理解析错误。

这是一个粗略的说明。它跳过空行,但假设在非空行中没有填充,只有标记。它假设块不能嵌套:

#include <fstream>
#include <iostream>

enum parse_error
{
    none,
    open_fail,
    nested_starttag,
    orphan_endtag,
    orphan_starttag
};

void handle_out_of_block_line(std::string const & line) 
{
    std::cout << "Read out-of-block line: \"" << line << '\"' << std::endl;
}

void handle_in_block_line(std::string const & line, std::string & block) 
{
    std::cout << "Read in-block line: \"" << line << '\"' << std::endl;
    block += line + '\n'; 
}

void handle_block(std::string const & block)
{
    std::cout << "Got block {\n" << block << "}" << std::endl;
}

parse_error parse(std::string const & filename)
{
    std::ifstream ifs(filename);
    if (!ifs) {
        std::cerr << 
        "error: cannot open \"" << filename << "\" for reading" << std::endl; 
        return parse_error::open_fail;
    }
    bool in_block = 0;  
    std::string line;
    std::string block;
    while(getline(ifs,line),ifs) {
        if (line.empty()) {
            continue; // Skip empty line.
        }
        if (line == "starttag") {
            if (in_block) {
                std::cerr << "error: starttag within starttag" << std::endl; 
                return parse_error::nested_starttag;
            }
            in_block = true;
            block.clear();
        }
        if (in_block) {
            handle_in_block_line(line,block);
        } else {
            handle_out_of_block_line(line);
        }
        if (line == "endtag") {
            if (!in_block) {
                std::cerr << "error: ophan endtag" << std::endl; 
                return parse_error::orphan_endtag;
            }
            in_block = false;
            handle_block(block);
        }
    }
    if (in_block) {
        std::cerr << "error: ophan starttag" << std::endl;
        return parse_error::orphan_starttag;
    }
    return parse_error::none;
}

int main(int argc, char const *argv[])
{
    return parse(argv[1]);
}

输入例如包含以下内容的文件:

some text
more text
starttag
much stuff 
on many lines
endtag
even more text

它输出这个:

Read out-of-block line: "some text"
Read out-of-block line: "more text"
Read in-block line: "starttag"
Read in-block line: "much stuff "
Read in-block line: "on many lines"
Read in-block line: "endtag"
Got block {
starttag
much stuff 
on many lines
endtag
}
Read out-of-block line: "even more text"
于 2013-05-18T09:06:20.410 回答
0

你似乎(对我来说)选择了一种相对困难的方法来解决这个问题。

既然您要扫描文件以查找标签,为什么不在扫描标签时保留数据呢?也就是说,扫描并丢弃数据,直到找到开始标记,然后继续扫描并从那里保留数据,直到找到结束标记。

于 2013-05-16T16:49:16.013 回答