使用fstream读取完整文件到字符串的最佳方式

Optimal way of reading a complete file to a string using fstream?

本文关键字:字符串 最佳 方式 文件 fstream 读取 使用      更新时间:2023-10-16

许多其他的帖子,如"将整个ASCII文件读取为c++ std::string "解释了一些选项是什么,但没有深入描述各种方法的优缺点。我想知道为什么一种方法优于另一种方法?

所有这些都使用std::fstream将文件读入std::string。我不确定每种方法的成本和收益。让我们假设这是一种常见的情况,在这种情况下,读取文件的大小很小,内存可以很容易地容纳,显然,无论你怎么做,读取一个多tb的文件到内存中都是一个坏主意。

经过几次google搜索后,最常见的将整个文件读入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')的其他方法的实际原因吗?

这些方法与其他一次性读取文件的方法相比如何呢?

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

这似乎是最好的。它将几乎所有的工作都转移到标准库上,而标准库应该针对给定的平台进行大量优化。除了流有效性和文件结束外,我看不出检查的理由。这是理想的吗?还是存在未被发现的问题?

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

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

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

最快 - TellSeekRead和CTellSeekRead-这些系统提供了一个容易获得的大小和读取文件在一次。

更快 - Getline追加和Eof -检查字符似乎不施加任何成本

快速 - RdbufMove和Rdbuf - std::move在发布中似乎没有区别。

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

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

至于衡量简单性和习惯性,我们如何客观地衡量它们?简单似乎是可行的,也许使用一些行loc和圈复杂度,但如何习惯的东西似乎纯粹是主观的。

什么是最简单、最习惯、最好的表现和标准将整个文件读取为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);
}   

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

这个网站对几种不同的方法进行了很好的比较。我目前使用的是:

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

你的问题有两大难点。首先,标准没有强制要求任何特定的实现(是的,几乎每个人都从相同的实现开始;但是随着时间的推移,他们一直在修改它,比如说,NTFS的最佳I/O代码将不同于ext4的最佳I/O代码,因此有可能(尽管有些不太可能)某种特定方法在一个平台上最快,而在另一个平台上却不行。其次,定义"最优"有点困难;我猜你的意思是"最快",但事实并非如此。

有一些方法是惯用的,并且非常好的c++,但不太可能提供出色的性能。如果您的目标是最终使用单个std::string,那么使用std::getline(std::ostream&, std::string&)很可能会比必要时慢。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<FILE>),因为至少在Windows上,当std::fopen()失败时,你会得到一个更好的错误消息,而不是在构建文件流对象失败时。在决定哪种方法是最佳方法时,我认为更好的错误消息是一个重要因素。