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

当前回答

我知道我迟到了,但现在(2021年)在我的机器上,这是我测试过的最快的实现:

#include <fstream>
#include <string>

bool fileRead( std::string &contents, const std::string &path ) {
    contents.clear();
    if( path.empty()) {
        return false;
    }
    std::ifstream stream( path );
    if( !stream ) {
        return false;
    }
    stream >> contents;
    return true;
}

其他回答

基于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()会一点一点地编译成几乎相同的东西。

最短的变体:Live On Coliru

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

它需要头文件<iterator>。

有一些报告说,这种方法比预先分配字符串和使用std::istream::read要慢。然而,在现代的编译器上,这种情况似乎不再存在,尽管各种方法的相对性能似乎高度依赖于编译器。

如果你有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数据。

#include <string>
#include <fstream>

int main()
{
    std::string fileLocation = "C:\\Users\\User\\Desktop\\file.txt";
    std::ifstream file(fileLocation, std::ios::in | std::ios::binary);

    std::string data;

    if(file.is_open())
    {
        std::getline(file, data, '\0');

        file.close();
    }
}

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

请注意,tellg()会在出错时返回-1。如果将tellg()的结果作为分配参数传递,则应该首先对结果进行完整性检查。

这个问题的一个例子:

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

在上面的例子中,如果tellg()遇到错误,它将返回-1。在signed(即tellg()的结果)和unsigned(即vector<char>构造函数的arg)之间隐式强制转换将导致your vector错误地分配大量字节。(可能是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);
}