我如何读一个文件到一个std::字符串,即,读取整个文件一次?
文本或二进制模式应该由调用者指定。解决方案应该是符合标准的、可移植的和高效的。它不应该不必要地复制字符串的数据,并且应该避免在读取字符串时重新分配内存。
一种方法是统计文件大小,调整std::string和fread()到std::string的const_cast<char*>()'ed data()。这要求std::string的数据是连续的,这不是标准所要求的,但它似乎是所有已知实现的情况。更糟糕的是,如果以文本模式读取文件,std::string的大小可能不等于文件的大小。
一个完全正确的、符合标准的、可移植的解决方案可以使用std::ifstream的rdbuf()构造成std::ostringstream,再从那里构造成std::string。但是,这可能会复制字符串数据和/或不必要地重新分配内存。
是否所有相关的标准库实现都足够智能以避免所有不必要的开销?
还有别的办法吗?
我是否错过了一些已经提供所需功能的隐藏Boost函数?
void slurp(std::string& data, bool is_binary)
基于CTT解决方案的更新函数:
#include <string>
#include <fstream>
#include <limits>
#include <string_view>
std::string readfile(const std::string_view path, bool binaryMode = true)
{
std::ios::openmode openmode = std::ios::in;
if(binaryMode)
{
openmode |= std::ios::binary;
}
std::ifstream ifs(path.data(), openmode);
ifs.ignore(std::numeric_limits<std::streamsize>::max());
std::string data(ifs.gcount(), 0);
ifs.seekg(0);
ifs.read(data.data(), data.size());
return data;
}
有两个重要的区别:
Tellg()不保证返回自文件开始以来的字节偏移量。相反,正如Puzomor Croatia所指出的,它更像是一个可以在fstream调用中使用的令牌。但是Gcount()会返回上次提取的未格式化字节数。因此,我们打开文件,使用ignore()提取并丢弃其所有内容,以获得文件的大小,并基于此构造输出字符串。
其次,我们通过直接写入字符串来避免必须将文件的数据从std::vector<char>复制到std::string。
就性能而言,这应该是绝对最快的,提前分配适当大小的字符串并调用read()一次。有趣的是,在gcc上使用ignore()和countg()而不是ate和tellg()会一点一点地编译成几乎相同的东西。
从几个地方提取信息…这应该是最快最好的方法:
#include <filesystem>
#include <fstream>
#include <string>
//Returns true if successful.
bool readInFile(std::string pathString)
{
//Make sure the file exists and is an actual file.
if (!std::filesystem::is_regular_file(pathString))
{
return false;
}
//Convert relative path to absolute path.
pathString = std::filesystem::weakly_canonical(pathString);
//Open the file for reading (binary is fastest).
std::wifstream in(pathString, std::ios::binary);
//Make sure the file opened.
if (!in)
{
return false;
}
//Wide string to store the file's contents.
std::wstring fileContents;
//Jump to the end of the file to determine the file size.
in.seekg(0, std::ios::end);
//Resize the wide string to be able to fit the entire file (Note: Do not use reserve()!).
fileContents.resize(in.tellg());
//Go back to the beginning of the file to start reading.
in.seekg(0, std::ios::beg);
//Read the entire file's contents into the wide string.
in.read(fileContents.data(), fileContents.size());
//Close the file.
in.close();
//Do whatever you want with the file contents.
std::wcout << fileContents << L" " << fileContents.size();
return true;
}
这将宽字符读入std::wstring,但如果您只想要常规字符和std::string,则可以很容易地进行调整。
如果你有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, std::ios::in | std::ios::binary);
// Obtain the size of the file.
const auto sz = fs::file_size(path);
// Create a buffer.
std::string result(sz, '\0');
// Read the whole file into the buffer.
f.read(result.data(), sz);
return result;
}
注意:如果你的标准库还不完全支持c++ 17,你可能需要使用<experimental/filesystem>和std::experimental::filesystem。你可能还需要用&result[0]替换result.data(),如果它不支持非const std::basic_string数据。