我如何读一个文件到一个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)
由于这似乎是一个广泛使用的实用程序,我的方法是搜索并选择已经可用的库,而不是手工制作的解决方案,特别是如果boost库已经在您的项目中链接(链接器标志-lboost_system -lboost_filesystem)。在这里(以及旧的boost版本),boost提供了一个load_string_file实用程序:
#include <iostream>
#include <string>
#include <boost/filesystem/string_file.hpp>
int main() {
std::string result;
boost::filesystem::load_string_file("aFileName.xyz", result);
std::cout << result.size() << std::endl;
}
作为一个优点,这个函数不寻求整个文件来确定大小,而是在内部使用stat()。然而,一个可能可以忽略不计的缺点是,在检查源代码时可以很容易地推断出:字符串不必要地用'\0'字符来调整大小,而'\0'字符是由文件内容重写的。
这是我使用的函数,当处理大文件(1GB+)时,由于某种原因std::ifstream::read()比std::ifstream::rdbuf()快得多,当你知道文件大小时,所以整个“先检查文件大小”的事情实际上是一个速度优化
#include <string>
#include <fstream>
#include <sstream>
std::string file_get_contents(const std::string &$filename)
{
std::ifstream file($filename, std::ifstream::binary);
file.exceptions(std::ifstream::failbit | std::ifstream::badbit);
file.seekg(0, std::istream::end);
const std::streampos ssize = file.tellg();
if (ssize < 0)
{
// can't get size for some reason, fallback to slower "just read everything"
// because i dont trust that we could seek back/fourth in the original stream,
// im creating a new stream.
std::ifstream file($filename, std::ifstream::binary);
file.exceptions(std::ifstream::failbit | std::ifstream::badbit);
std::ostringstream ss;
ss << file.rdbuf();
return ss.str();
}
file.seekg(0, std::istream::beg);
std::string result(size_t(ssize), 0);
file.read(&result[0], std::streamsize(ssize));
return result;
}
一种方法是将流缓冲区刷新到一个单独的内存流中,然后将其转换为std::string(错误处理省略):
std::string slurp(std::ifstream& in) {
std::ostringstream sstr;
sstr << in.rdbuf();
return sstr.str();
}
这是非常简洁的。然而,正如问题中所指出的那样,这执行了冗余拷贝,不幸的是,基本上没有办法省略这个拷贝。
不幸的是,避免冗余拷贝的唯一真正解决方案是在循环中手动读取。由于c++现在保证了连续的字符串,可以编写以下代码(≥c++ 17,包含错误处理):
auto read_file(std::string_view path) -> std::string {
constexpr auto read_size = std::size_t(4096);
auto stream = std::ifstream(path.data());
stream.exceptions(std::ios_base::badbit);
auto out = std::string();
auto buf = std::string(read_size, '\0');
while (stream.read(& buf[0], read_size)) {
out.append(buf, 0, stream.gcount());
}
out.append(buf, 0, stream.gcount());
return out;
}
我知道这是一个非常古老的问题,有很多答案,但没有一个人提到我认为最明显的方法。是的,我知道这是c++,使用libc是邪恶和错误的,但这是疯狂的。使用libc很好,特别是对于这样简单的事情。
本质上:只需打开文件,获取它的大小(不一定是按这个顺序),然后读取它。
#include <cstdio>
#include <cstdlib>
#include <cstring>
#include <sys/stat.h>
static constexpr char const filename[] = "foo.bar";
int main(void)
{
FILE *fp = ::fopen(filename, "rb");
if (!fp) {
::perror("fopen");
::exit(1);
}
struct stat st;
if (::fstat(fileno(fp), &st) == (-1)) {
::perror("fstat");
::exit(1);
}
// You could simply allocate a buffer here and use std::string_view, or
// even allocate a buffer and copy it to a std::string. Creating a
// std::string and setting its size is simplest, but will pointlessly
// initialize the buffer to 0. You can't win sometimes.
std::string str;
str.reserve(st.st_size + 1U);
str.resize(st.st_size);
::fread(str.data(), 1, st.st_size, fp);
str[st.st_size] = '\0';
::fclose(fp);
}
除了(在实践中)完全可移植之外,这看起来并不比其他一些解决方案更糟糕。当然,也可以抛出异常,而不是立即退出。它严重激怒我,调整std::string总是0初始化它,但这是没有办法的。
请注意,这只适用于c++ 17及以后的版本。早期版本(应该)禁止编辑std::string::data()。如果使用较早的版本,可以考虑使用std::string_view或简单地复制一个原始缓冲区。