在C ++中将整个文件读入std :: string的最好方法是什么?
如何将文件读入std::string
,即一次读取整个文件?
文本或二进制模式应由调用者指定。 解决scheme应符合标准,便携和高效。 它不应该不必要地复制string的数据,而应该避免在读取string时重新分配内存。
一种方法是统计文件大小,将std::string
和fread()
到std::string
的const_cast<char*>()
'ed data()
。 这要求std::string
的数据是连续的,这是标准所不需要的,但对于所有已知的实现来说似乎都是这样。 更糟糕的是,如果在文本模式下读取文件, std::string
的大小可能不等于文件的大小。
一个完全正确的,符合标准的可移植解决scheme可以使用std::ifstream
的rdbuf()
构造成一个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
而不是seekg
和tellg
来获得文件的大小):
#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来啜泣。