如何在文本模式下安全地混合std :: ifstream的tellg,seekg和read(*,n)方法

时间:2013-05-16 16:39:41

标签: c++ text stl stream binary

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

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

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

显然,有很多变通方法,但我正在寻找一个漂亮而直观的解决方案。有什么建议吗?

EDIT1 @ 130507: 我的目标是保持std lib流并支持内存超速,我需要解析和处理两个部分,周围部分和标记之间的块。

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

以下是一些代码:

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

testfile可能如下所示:

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

4 个答案:

答案 0 :(得分:1)

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

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

答案 1 :(得分: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"

答案 2 :(得分: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;
        }
    }
}

答案 3 :(得分:0)

你似乎(对我而言)选择了一个相对困难的方法解决问题。

由于您要扫描文件以查找标记,为什么不保留数据作为扫描标记?也就是说,扫描并丢弃数据,直到找到开始标记,然后继续扫描并保留数据,直到找到结束标记。