在C ++中将整个文件读入std :: string的最好方法是什么?

如何将文件读入std::string ,即一次读取整个文件?

文本或二进制模式应由调用者指定。 解决scheme应符合标准,便携和高效。 它不应该不必要地复制string的数据,而应该避免在读取string时重新分配内存。

一种方法是统计文件大小,将std::stringfread()std::stringconst_cast<char*>() 'ed data() 。 这要求std::string的数据是连续的,这是标准所不需要的,但对于所有已知的实现来说似乎都是这样。 更糟糕的是,如果在文本模式下读取文件, std::string的大小可能不等于文件的大小。

一个完全正确的,符合标准的可移植解决scheme可以使用std::ifstreamrdbuf()构造成一个std::ostringstream并从那里转换成一个std::string 。 但是,这可能会复制string数据和/或不必要地重新分配内存。 所有相关的标准库实现是否足够聪明以避免所有不必要的开销? 还有另一种方法吗? 我错过了一些隐藏的Boost函数,它已经提供了所需的function吗?

请给出你的build议如何实现它。

 void slurp(std::string& data, bool is_binary) 

考虑到上面的讨论。

而最快(我知道,折扣内存映射文件):

 string str(static_cast<stringstream const&>(stringstream() << in.rdbuf()).str()); 

这需要额外的头文件<sstream>作为stringstream。 ( static_cast是必要的,因为operator <<返回一个普通的旧ostream&但是我们知道它实际上是一个stringstream&所以这个cast是安全的。)

分成多行,将临时移动到一个variables,我们得到一个更易读的代码:

 string slurp(ifstream& in) { stringstream sstr; sstr << in.rdbuf(); return sstr.str(); } 

或者,再一次在一行中:

 string slurp(ifstream& in) { return static_cast<stringstream const&>(stringstream() << in.rdbuf()).str(); } 

在类似的问题上看到这个答案 。

为了您的方便,我正在转贴CTT的解决scheme:

 string readFile2(const string &fileName) { ifstream ifs(fileName.c_str(), ios::in | ios::binary | ios::ate); ifstream::pos_type fileSize = ifs.tellg(); ifs.seekg(0, ios::beg); vector<char> bytes(fileSize); ifs.read(bytes.data(), fileSize); return string(bytes.data(), fileSize); } 

与其他答案相比,这个解决scheme的执行速度要快20%左右,而对Moby Dick(1.3M)文本的平均值为100。 对于一个可移植的C ++解决scheme不坏,我想看看mmap的文件的结果;)

最短的变体: Live Coliru

 std::string str(std::istreambuf_iterator<char>{ifs}, {}); 

它需要头<iterator>

有一些报告说这个方法比预分配string和使用std::istream::read要慢。 然而,在现代编译器上启用优化后,似乎不再是这种情况,尽pipe各种方法的相对性能似乎与编译器高度相关。

使用

 #include <iostream> #include <sstream> #include <fstream> int main() { std::ifstream input("file.txt"); std::stringstream sstr; while(input >> sstr.rdbuf()); std::cout << sstr.str() << std::endl; } 

或者非常接近的东西。 我没有打开stdlib参考来仔细检查我自己。

是的,我明白我没有按照要求写出slurp函数。

切勿写入std :: string的const char *缓冲区。 永远不能! 这样做是一个巨大的错误。

为std :: string中的整个string保留()空间,从合理大小的文件中将块读入缓冲区,然后append()。 块的大小取决于input文件的大小。 我很确定所有其他可移植和STL兼容的机制也会这样做(但看起来更漂亮)。

像这样的东西不应该太糟糕了:

 void slurp(std::string& data, const std::string& filename, bool is_binary) { std::ios_base::openmode openmode = ios::ate | ios::in; if (is_binary) openmode |= ios::binary; ifstream file(filename.c_str(), openmode); data.clear(); data.reserve(file.tellg()); file.seekg(0, ios::beg); data.append(istreambuf_iterator<char>(file.rdbuf()), istreambuf_iterator<char>()); } 

这样做的好处是,我们先保留储备,这样我们就不必在string中增加string。缺点是我们用字符来表示字符。 一个更聪明的版本可以抓住整个读取buf,然后调用下溢。

我没有足够的信誉来直接对使用tellg()响应发表评论。

请注意, tellg()错误时可以返回-1。 如果您将tellg()的结果作为分配parameter passing,则应首先检查结果。

问题的一个例子:

 ... std::streamsize size = file.tellg(); std::vector<char> buffer(size); ... 

在上面的例子中,如果tellg()遇到错误,它将返回-1。 在signed(即tellg() )和unsigned(即arg到vector<char>构造函数)的结果之间进行隐式转换将导致向量错误地分配大量的字节。 (可能是4294967295字节,或4GB。)

修改paxos1977的答案以解决上述问题:

 string readFile2(const string &fileName) { ifstream ifs(fileName.c_str(), ios::in | ios::binary | ios::ate); ifstream::pos_type fileSize = ifs.tellg(); if (fileSize < 0) <--- ADDED return std::string(); <--- ADDED ifs.seekg(0, ios::beg); vector<char> bytes(fileSize); ifs.read(&bytes[0], fileSize); return string(&bytes[0], fileSize); } 

您可以使用“std :: getline”函数,并指定“eof”作为分隔符。 由此产生的代码有点模糊:

 std::string data; std::ifstream in( "test.txt" ); std::getline( in, data, std::string::traits_type::to_char_type( std::string::traits_type::eof() ) ); 

此解决scheme将错误检查添加到基于rdbuf()的方法。

 std::string file_to_string(const std::string& file_name) { std::ifstream file_stream{file_name}; if (file_stream.fail()) { // Error opening file. } std::ostringstream str_stream{}; file_stream >> str_stream.rdbuf(); // NOT str_stream << file_stream.rdbuf() if (file_stream.fail() && !file_stream.eof()) { // Error reading file. } return str_stream.str(); } 

我添加了这个答案,因为添加错误检查到原来的方法并不像你所期望的那么微不足道。 原始方法使用stringstream的插入运算符( str_stream << file_stream.rdbuf() )。 问题是,当没有插入字符时,这会设置string的失败位。 这可能是由于错误,也可能是由于文件为空。 如果通过检查故障位来检查故障,当您读取空文件时,您会遇到误报。 你如何消除插入任何字符的合法失败和“失败”插入任何字符,因为该文件是空的?

你可能会想明确地检查一个空的文件,但这是更多的代码和相关的错误检查。

检查失败条件str_stream.fail() && !str_stream.eof()不起作用,因为插入操作没有设置eofbit(在ostringstream或ifstream上)。

所以,解决办法是改变操作。 而不是使用ostringstream的插入操作符(<<),使用ifstream的提取操作符(>>),它设置eofbit。 然后检查不合格的情况file_stream.fail() && !file_stream.eof()

重要的是,当file_stream >> str_stream.rdbuf()遇到合法故障时,它不应该设置eofbit(根据我对规范的理解)。 这意味着上述检查足以检测合法的失败。

如果你有C ++ 17(std :: filesystem),也有这种方式(通过std::filesystem::file_size而不是seekgtellg来获得文件的大小):

 #include <filesystem> #include <fstream> #include <string> namespace fs = std::filesystem; std::string readFile(fs::path path) { // Open the stream to 'lock' the file. std::ifstream f{ path }; // Obtain the size of the file. const auto sz = fs::file_size(path); // Create a buffer. std::string result(sz, ' '); // Read the whole file into the buffer. f.read(result.data(), sz); return result; } 

注意 :如果你的标准库还没有完全支持C ++ 17,你可能需要使用<experimental/filesystem>std::experimental::filesystem 。 如果不支持非常量std :: basic_string数据,则可能还需要将result.data()replace为&result[0]

如果你正在sl a一个11K的文件,那么你必须用一系列的块来完成,所以你必须使用类似std :: vector的东西,以大量的string来啜泣。