【问题标题】:Optimal way of reading a complete file to a string using fstream?使用 fstream 将完整文件读取到字符串的最佳方法?
【发布时间】:2015-11-17 03:31:21
【问题描述】:

许多其他帖子,如“Read whole ASCII file into C++ std::string”解释了一些选项是什么,但没有深入描述各种方法的优缺点。我想知道为什么一种方法比另一种更可取?

所有这些都使用std::fstream 将文件读入std::string。我不确定每种方法的成本和收益。让我们假设这是针对已知读取文件具有一些可以轻松容纳的小内存的常见情况,显然无论您如何将多 TB 文件读入内存都是一个坏主意。

在几次谷歌搜索后,将整个文件读入 std::string 的最常见方法是使用 std::getline 并在每行之后附加一个换行符。这对我来说似乎没有必要,但是否有一些性能或兼容性原因表明这是理想的?

std::string Results;
std::ifstream ResultReader("file.txt");    
while(ResultReader)
{
    std::getline(ResultReader, Results);
    Results.push_back('\n');
}

我拼凑的另一种方法是更改​​ getline 分隔符,使其不在文件中。 EOF 字符似乎不太可能位于文件中间,因此这似乎是一个可能的候选者。这包括强制转换,因此至少有一个不这样做的理由,但这确实会立即读取一个文件而没有字符串连接。据推测,分隔符检查仍有一些成本。是否还有其他充分的理由不这样做?

std::string Results;
std::ifstream ResultReader("file.txt");
std::getline(ResultReader, Results, (char)std::char_traits<char>::eof());

强制转换意味着在将 std::char_traits::eof() 定义为非 -1 的系统上可能会出现问题。与使用std::getlinestring::push_pack('\n') 的其他方法相比,这是不选择此方法的实际原因吗?

这些与其他一次读取文件的方式相比如何,例如这个问题:Read whole ASCII file into C++ std::string

std::ifstream ResultReader("file.txt");
std::string Results((std::istreambuf_iterator<char>(ResultReader)),
                     std::istreambuf_iterator<char>());

这似乎是最好的。它将几乎所有的工作卸载到标准库上,标准库应该针对给定的平台进行大量优化。除了流有效性和文件结尾之外,我认为没有其他检查的理由。这是理想的还是存在看不见的问题。

某些实现的标准或细节是否提供了偏好某种方法而不是另一种方法的理由?我是否错过了一些在各种情况下可能证明是理想的方法?

将整个文件读入std::string 的最简单、最惯用、性能最佳且符合标准的方法是什么?

编辑 - 2 这个问题促使我编写了一小套基准测试。它们是 MIT 许可证,可在 github 上获取:https://github.com/Sqeaky/CppFileToStringExperiments

Fastest - TellSeekRead 和 CtellSeekRead- 系统提供了一个简单的获取大小并一次读取文件的方法。

更快 - Getline Appending 和 Eof - 字符检查似乎不会产生任何成本。

Fast - RdbufMove 和 Rdbuf - std::move 似乎对发布没有影响。

- 迭代器、BackInsertIterator 和 AssignIterator - 迭代器和输入流有问题。记忆中的工作很棒,但不是在这里。也就是说,其中一些比其他更快。

到目前为止,我已经添加了所有建议的方法,包括链接中的方法。如果有人可以在 Windows 和其他编译器上运行它,我将不胜感激。我目前无法访问具有 NTFS 的机器,并且已经注意到这一点和编译器细节可能很重要。

至于衡量简单性和惯用性,我们如何客观地衡量这些?简单似乎是可行的,也许使用一些线 LOCs 和 Cyclomatic 复杂性,但某些东西是多么地道似乎纯粹是主观的。

【问题讨论】:

  • 链接的答案使用 seek/tell 来查找文件的长度。如果你知道它是一个常规文件,使用 stat 会更简单。
  • stat 符合标准,但标准是 POSIX。
  • 我所怀疑的。我正在限定斯塔克的评论。
  • 我应该@replied 给你们两个,我不是要挑剔任何人。即便如此,stat 对许多人来说也是一个可行的答案。

标签: c++ file input


【解决方案1】:

什么是最简单、最惯用、性能最好的标准 将整个文件读入 std::string 的兼容方式?

这些是完全矛盾的要求,一个最有可能减少另一个。更简单的代码不会是最快的,也不是更惯用的。

在探索了这个领域一段时间后,我得出了一些结论:
1) 导致性能损失最大的是 IO 操作本身 - 所采取的 IO 操作越少 - 代码速度最快
2) 内存分配也相当昂贵,但不如 IO
3) 以二进制方式读取比以文本方式读取要快
4) 使用 OS API 可能会比 C++ 流更快
5) std::ios_base::sync_with_stdio 并没有真正影响表演,这是一个都市传说。

如果由于以下原因需要性能,使用std::getline 可能不是最佳选择:它将为 N 行执行 N IO 操作和 N 分配。

一种快速、标准和优雅的折衷方案是获取文件大小,一次性分配所有内存,然后一次性读取文件:

std::ifstream fileReader(<your path here>,std::ios::binary|std::ios::ate);
if (fileReader){
  auto fileSize = fileReader.tellg();
  fileReader.seekg(std::ios::beg);
  std::string content(fileSize,0);
  fileReader.read(&content[0],fileSize);
}   

移动内容以防止不需要的副本。

【讨论】:

  • 我将此添加到我在问题中链接的基准套件中。我同意这种方法很好,也是迄今为止最快的方法,但我不同意你的一些观点。我不认为二进制比文本快,在 1000 次迭代中,我没有看到毫秒的差异。我认为我的整个问题的答案可能与您的第 1 点一样简单。
  • std::string(size_t, char) 构造函数不仅分配和设置大小,而且用给定的字符填充分配的内存。我会使用std::unique_ptr&lt;char[]&gt;(new char[fileSize]);make_unique - 这样您将获得异常安全性并避免使用'\0' 初始化潜在的大缓冲区
  • 在块内定义content会在块结束时销毁它,所以任何使用它的代码都需要在那个块中,对吧?
【解决方案2】:

This website 对几种不同的方法进行了很好的比较。我目前使用的是:

std::string read_sequence() {
    std::ifstream f("sequence.fasta");
    std::ostringstream ss;
    ss << f.rdbuf();
    return ss.str();
}

如果您的文本文件由换行符分隔,这将保留它们。例如,如果您想删除它(大多数情况下是我的情况),您可以添加对诸如

之类的调用
auto s = ss.str();
s.erase(std::remove_if(s.begin(), s.end(), 
        [](char c) { return c == '\n'; }), s.end());

【讨论】:

  • 我会阅读你的网站,谢谢你的 lambda remove_if 表达式,这是一个简单的实现这样的任务。您对 stringstream 方法的读取缓冲区似乎与 Max 的方法没有本质上的不同,std::move 似乎没有做任何一个好的编译器还没有做的事情。我添加了 RdbufMove 作为基准套件的测试,这个问题让我写:github.com/Sqeaky/CppFileToStringExperiments
  • 使用 mmap 和子类字符串来正常运行。 Windows appears to have a similar 设施。
  • @msw 我不知道你说的是什么意思,我也无法访问 Windows 机器。你能解释一下吗?
  • @Sqeaky 你是对的,那里的 std::move 是不必要的。感谢您指出:-)
【解决方案3】:

你的问题有两个很大的困难。首先,该标准没有强制要求任何特定的实现(是的,几乎每个人都从相同的实现开始;但随着时间的推移,他们一直在修改它,例如,NTFS 的最佳 I/O 代码将不同于最佳的ext4 的 I/O 代码),因此有可能(尽管不太可能)一种特定方法在一个平台上最快,但在另一个平台上却不是。第二,“最优”的定义有点困难;我假设您的意思是“最快”,但不一定如此。

有些方法是惯用的,而且 C++ 非常好,但不太可能提供出色的性能。如果您的目标是最终得到一个 std::string,那么使用 std::getline(std::ostream&amp;, std::string&amp;) 很可能会比必要的慢。 std::getline() 调用必须查找'\n',并且您偶尔会重新分配和复制目标std::string。即便如此,它还是简单得可笑,而且容易理解。从维护的角度来看,这可能是最佳的,假设您不需要绝对最快的性能。如果您一次需要将整个文件放在一个巨大的std::string 中,这也是一种好方法。你会非常节省记忆力。

一种可能更有效的方法是操作读取缓冲区:

std::string read_the_whole_file(std::ostream& ostr)
{
    std::ostringstream sstr;
    sstr << ostr.rdbuf();
    return sstr.str();
}

就我个人而言,我同样可能使用std::fopen()std::fread()(和std::unique_ptr&lt;FILE&gt;),因为至少在Windows 上,当std::fopen() 失败时,您会得到比构造一个更好的错误消息文件流对象失败。在决定哪种方法最佳时,我认为更好的错误消息是一个重要因素。

【讨论】:

  • 我把这个和我写的 3 个方法写成了一个微基准:github.com/Sqeaky/CppFileToStringExperiments。您是否可以访问具有 NTFS 的机器?我不。不知何故,这两种朴素的 getline 策略是最快的,然后直接访问读取缓冲区稍微慢了一点,但可以衡量的是,最后迭代器方法非常慢。我同意错误信息很重要,但其质量很难凭经验衡量。
猜你喜欢
  • 2013-12-10
  • 1970-01-01
  • 1970-01-01
  • 1970-01-01
  • 1970-01-01
  • 2011-06-28
  • 1970-01-01
  • 1970-01-01
  • 2011-04-02
相关资源
最近更新 更多