我如何读一个文件到一个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)

当前回答

#include <string>
#include <sstream>

using namespace std;

string GetStreamAsString(const istream& in)
{
    stringstream out;
    out << in.rdbuf();
    return out.str();
}

string GetFileAsString(static string& filePath)
{
    ifstream stream;
    try
    {
        // Set to throw on failure
        stream.exceptions(fstream::failbit | fstream::badbit);
        stream.open(filePath);
    }
    catch (system_error& error)
    {
        cerr << "Failed to open '" << filePath << "'\n" << error.code().message() << endl;
        return "Open fail";
    }

    return GetStreamAsString(stream);
}

用法:

const string logAsString = GetFileAsString(logFilePath);

其他回答

就性能而言,我还没有找到比下面的代码更快的代码。

std::string readAllText(std::string const &path)
{
    assert(path.c_str() != NULL);
    FILE *stream = fopen(path.c_str(), "r");
    assert(stream != NULL);
    fseek(stream, 0, SEEK_END);
    long stream_size = ftell(stream);
    fseek(stream, 0, SEEK_SET);
    void *buffer = malloc(stream_size);
    fread(buffer, stream_size, 1, stream);
    assert(ferror(stream) == 0);
    fclose(stream);
    std::string text((const char *)buffer, stream_size);
    assert(buffer != NULL);
    free((void *)buffer);
    return text;
}

由于这似乎是一个广泛使用的实用程序,我的方法是搜索并选择已经可用的库,而不是手工制作的解决方案,特别是如果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;
}

请看类似问题的答案。

为了方便大家,我转发了CTT的解决方案:

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);
}

当对《白鲸记》(Moby Dick, 1.3M)的文本进行平均100次运行时,该解决方案比本文给出的其他答案的执行时间快了约20%。对于一个可移植的c++解决方案来说还不错,我想看看mmap'ing文件的结果;)