【问题标题】:Off-by-one errors when reading a file one chunk at a time一次读取一个文件时出现一个错误
【发布时间】:2021-02-21 23:50:42
【问题描述】:

ifstream::readsome 由于实现定义的问题而臭名昭著地不利于读取文件块。就我而言,MSVC 在新打开的文件上返回 0。

我查看了他们的实现并意识到他们只是在后台调用ifstream::read

MSVC 实施

streamsize __CLR_OR_THIS_CALL readsome(_Elem* _Str,
    streamsize _Count) { // read up to _Count characters into buffer, without blocking
    ios_base::iostate _State = ios_base::goodbit;
    _Chcount                 = 0;
    const sentry _Ok(*this, true);
    streamsize _Num;

    if (!_Ok) {
        _State |= ios_base::failbit; // no buffer, fail
    } else if ((_Num = _Myios::rdbuf()->in_avail()) < 0) {
        _State |= ios_base::eofbit; // no characters available
    } else if (0 < _Count && 0 < _Num) { // read available
        read(_Str, _Num < _Count ? _Num : _Count);
    }

    _Myios::setstate(_State);
    return gcount();
}

所以我实现了自己的,只调用ifstream::read

我的实现

std::optional<std::string> ReadSomeStringFromFile(std::ifstream& ifs, std::streampos pos, std::streamsize count) noexcept {
    if(ifs && ifs.is_open()) {
        auto result = std::string(count, '\0');
        ifs.seekg(pos);
        ifs.read(result.data(), count);
        if(ifs.gcount()) {
            return result;
        }
    }
    return {};
}

用法:

std::streampos pos{0};
std::streamsize count{10};
std::ifstream ifs{g_options_filepath};
{
    auto stream = FileUtils::ReadSomeStringFromFile(ifs, pos, count);
    while(stream.has_value()) {
        DebuggerPrintf(stream.value().c_str());
        pos += count;
        stream = FileUtils::ReadSomeStringFromFile(ifs, pos, count);
    }
}

这适用于二进制文件(我有一个单独的函数),但对于字符串版本 我需要保留换行符的地方如果块会产生一个错误包含换行符。这会导致块中的最后一个字符被复制为下一个字符中的第一个字符:

预期输出

difficulty=Normal
controlpref=Mouse
sound=5
music=5
cameraShakeStrength=1.000000

实际输出

difficulty=Normal
coontrolpref=Mouse
souund=5
musiic=5
camerraShakeStrength=1.000000

使用格式化的ifstream::get 默认使用换行符作为分隔符并完全跳过它(同样,换行符需要保留)并导致交错输出和丢弃字符:

difficult=Normalontrolpre=Mouseund=5ic=5raShakeStength=1.00000

问题

有没有办法尝试对格式化数据使用未格式化的输入函数,或者我不应该尝试对文本数据进行此操作?

【问题讨论】:

  • 确保您以ios_base::binary 模式打开文件,否则您不能假设seekg 使用绝对文件偏移量(并且您必须记住tellgread 之后的位置, 而不是)。
  • @dxiv 这不会破坏尝试将文件作为文本读取的目的吗?照原样,从&lt;CR&gt;&lt;LF&gt; 换行对中读取二进制重复&lt;CR&gt; 并导致&lt;CR&gt;&lt;CR&gt;&lt;LF&gt; 被输出。从表面上看,这看起来像是在复制换行符。我想我可以一次手动使用一个字符到pos,但我担心从文件中间开始时会很慢并导致问题。
  • reading as binary duplicates &lt;CR&gt; from the &lt;CR&gt;&lt;LF&gt; 以二进制模式读取字面意思是读取磁盘上的内容。如果您得到&lt;CR&gt; 重复,那么在您读取原始数据后会在其他地方发生这种情况。另一点仍然是,如果您在文本模式下使用它,那么您只能 seekgtellg 返回的位置,不能基于字节数。
  • @dxiv 我认为我的解决方案会奏效。有什么建议吗?
  • @dxiv 鉴于标题和最后的问题,我认为它可以归结为:“我在尝试保留空白时遇到了一个错误。我用错了吗?”回答:“是的。你做错了。你应该这样做。”

标签: c++ file c++17 file-read


【解决方案1】:

我不经常使用get,所以我忘记了它的存在。使用this answer as a guide 我想出了一个解决方案:

(我仔细检查过,使用FormattedInput 作为(ifs &gt;&gt; std::noskipws &gt;&gt; ch) 的另一个答案给出了相同的结果,即使get 规范说它将它视为UnformattedInput

[[nodiscard]] std::optional<std::string> ReadSomeStringBufferFromFile(std::ifstream& ifs, std::streampos pos, std::streamsize count /*= 0u*/) noexcept {
    if(!(ifs && ifs.is_open())) {
        return {};
    }
    ifs.seekg(pos, std::ios::beg);

    //Would like to early-out here,
    //but MSVC ifstream::seekg doesn't set the fail bit,
    //so can't early-out until the get call.

    char ch{};
    std::string result{};
    result.reserve(count);
    bool readsome{false}; //If nothing read, make std::optional::has_value false.
    while(ifs && ifs.get(ch) && count > 0) {
        result.append(1, ch);
        --count;
        readsome |= true;
    }
    return readsome ? std::make_optional(result) : std::nullopt;

【讨论】:

    猜你喜欢
    • 1970-01-01
    • 2020-12-24
    • 1970-01-01
    • 1970-01-01
    • 2021-11-19
    • 1970-01-01
    • 2019-07-14
    • 1970-01-01
    • 1970-01-01
    相关资源
    最近更新 更多