许多其他帖子,如"将整个ASCII文件读入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');
}
Run Code Online (Sandbox Code Playgroud)
我拼凑在一起的另一种方法是更改getline分隔符,使其不在文件中.EOF字符似乎不太可能在文件的中间,所以这似乎是一个可能的候选人.这包括一个强制转换,所以至少有一个原因不这样做,但这确实会立即读取一个文件而没有字符串连接.据推测,分隔符检查仍然需要一些费用.有没有其他充分理由不这样做?
std::string Results;
std::ifstream ResultReader("file.txt");
std::getline(ResultReader, Results, (char)std::char_traits<char>::eof());
Run Code Online (Sandbox Code Playgroud)
转换意味着在将std :: char_traits :: eof()定义为-1以外的系统上可能会出现问题.这是一个实际的理由,不选择使用std::getline
和其他方法string::push_pack('\n')
.
如何在这个问题中将这些文件与其他读取文件的方法进行比较:将整个ASCII文件读入C++ std :: string
std::ifstream ResultReader("file.txt");
std::string Results((std::istreambuf_iterator<char>(ResultReader)),
std::istreambuf_iterator<char>());
Run Code Online (Sandbox Code Playgroud)
这似乎是最好的.它将几乎所有工作卸载到标准库上,该库应该针对给定平台进行大量优化.除了流有效性和文件结尾之外,我认为没有理由进行检查.这是理想的还是存在看不见的问题.
标准或某些实现的细节是否提供了偏好某种方法而不是另一种方法的理由?我是否错过了一些可能在各种情况下都很理想的方法?
什么是最简单,最惯用,最佳性能和标准兼容的方式将整个文件读入std::string
?
编辑 - 2 这个问题促使我写了一小套基准.它们是MIT许可证,可在github上获得:https://github.com/Sqeaky/CppFileToStringExperiments
最快 - TellSeekRead和CTellSeekRead-这些系统提供了一个容易获得的大小并一次读取文件.
更快 - Getline追加和Eof - 检查字符似乎没有任何成本.
快 - RdbufMove和Rdbuf - std :: move似乎在发布中没有任何区别.
慢 - Iterator,BackInsertIterator和AssignIterator - 迭代器和输入流有问题.这项工作在记忆中很棒,但不在这里.也就是说其中一些比其他更快.
我已添加到目前为止建议的所有方法,包括链接中的方法.如果有人可以在Windows和其他编译器上运行它,我将不胜感激.我目前无法访问具有NTFS的计算机,并且已经注意到这个和编译器的详细信息可能很重要.
至于衡量简单性和惯用性,我们如何客观地衡量这些?简单似乎可行,也许使用行LOC和Cyclomatic复杂性,但惯用的东西似乎纯粹是主观的.
将整个文件读入 std::string 的最简单、最惯用、性能最佳且符合标准的方法是什么?
这些都是非常矛盾的要求,其中一项很可能会削弱另一项的要求。更简单的代码不会是最快的,也不会更惯用。
在探索这个领域一段时间后,我得出了一些结论:
1)造成最大性能损失的是 IO 操作本身 - 执行的 IO 操作越少 - 代码速度最快
2)内存分配也相当昂贵,但没有那么昂贵因为 IO
3) 读取二进制文件比读取文本文件更快
4) 使用操作系统 API 可能比 C++ 流更快
5)std::ios_base::sync_with_stdio
并没有真正影响性能,这是一个都市传说。
std::getline
如果由于以下原因需要性能,则using可能不是最佳选择:它将为 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);
}
Run Code Online (Sandbox Code Playgroud)
移动内容以防止不需要的副本。
你的问题有两个很大的困难。首先,该标准没有强制要求任何特定的实现(是的,几乎每个人都从相同的实现开始;但随着时间的推移,他们一直在修改它,例如,NTFS 的最佳 I/O 代码将不同于最佳的 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();
}
Run Code Online (Sandbox Code Playgroud)
就我个人而言,我同样可能使用std::fopen()
and std::fread()
(and std::unique_ptr<FILE>
),因为至少在 Windows 上,std::fopen()
失败时您会得到比构造文件流对象失败时更好的错误消息。我认为更好的错误消息是决定哪种方法最佳的重要因素。