234

如何将文件读入std::string. 即一次读取整个文件?

调用者应指定文本或二进制模式。该解决方案应符合标准、可移植且高效。它不应该不必要地复制字符串的数据,并且应该避免在读取字符串时重新分配内存。

一种方法是统计文件大小,将std::string和调整fread()std::string' const_cast<char*>()ed data()。这要求std::string's 的数据是连续的,这不是标准所要求的,但似乎所有已知的实现都是这种情况。更糟糕的是,如果以文本模式读取文件,则std::string's 的大小可能不等于文件的大小。

可以使用std::ifstream's rdbuf()into astd::ostringstream和从那里到std::string. 但是,这可能会复制字符串数据和/或不必要地重新分配内存。

  • 所有相关的标准库实现是否足够聪明,可以避免所有不必要的开销?
  • 还有另一种方法吗?
  • 我是否错过了一些已经提供所需功能的隐藏 Boost 功能?


void slurp(std::string& data, bool is_binary)
4

21 回答 21

158

一种方法是将流缓冲区刷新到单独的内存流中,然后将其转换为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;
}
于 2008-09-22T17:22:30.033 回答
73

最短的变体:Live On Coliru

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

它需要标题<iterator>

有一些报道称这种方法比预分配字符串和使用std::istream::read. 然而,在启用了优化的现代编译器上,情况似乎不再如此,尽管各种方法的相对性能似乎高度依赖于编译器。

于 2008-09-22T17:13:40.553 回答
55

有关类似问题,请参阅此答案。

为了您的方便,我重新发布 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++ 解决方案来说还不错,我想看看对文件进行映射的结果;)

于 2009-02-08T03:27:07.020 回答
36

如果您有 C++17 (std::filesystem),还有这种方式(通过std::filesystem::file_size而不是seekgand获取文件的大小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::basic_string datastd::experimental::filesystem ,您可能还需要替换result.data()为。&result[0]

于 2016-12-01T05:53:44.650 回答
26

利用

#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按要求编写函数。

于 2008-09-22T16:57:43.167 回答
17

我没有足够的声誉直接评论使用tellg().

请注意,tellg()错误时可以返回 -1。如果您将结果tellg()作为分配参数传递,您应该首先检查结果。

问题的一个例子:

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

在上面的示例中,如果tellg()遇到错误,它将返回 -1。tellg()有符号(即 的结果)和无符号(即构造函数的 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);
}
于 2017-03-24T21:07:34.377 回答
8

此解决方案将错误检查添加到基于 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())。问题是这会在没有插入字符时设置字符串流的故障位。这可能是由于错误,也可能是由于文件为空。如果您通过检查故障位来检查故障,则在读取空文件时会遇到误报。您如何区分由于文件为空而无法插入任何字符的合法失败和插入任何字符的“失败”?

您可能会考虑显式检查空文件,但这需要更多代码和相关的错误检查。

检查失败条件str_stream.fail() && !str_stream.eof()不起作用,因为插入操作没有设置 eofbit(在 ostringstream 或 ifstream 上)。

所以,解决办法是改变操作。不要使用 ostringstream 的插入运算符 (<<),而是使用 ifstream 的提取运算符 (>>),它确实设置了 eofbit。然后检查故障情况file_stream.fail() && !file_stream.eof()

重要的是,当file_stream >> str_stream.rdbuf()遇到合法失败时,它不应该设置 eofbit (根据我对规范的理解)。这意味着上述检查足以检测合法故障。

于 2017-03-26T10:15:05.530 回答
6

由于这似乎是一个广泛使用的实用程序,我的方法是搜索并更喜欢已经可用的库而不是手工制作的解决方案,特别是如果您的项目中已经链接了 boost 库(链接器标志 -lboost_system -lboost_filesystem)。在这里(以及旧的 bo​​ost 版本),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'被文件内容重写的字符调整大小。

于 2020-09-11T13:24:21.087 回答
6

这是一个使用新文件系统库的版本,具有相当强大的错误检查功能:

#include <cstdint>
#include <exception>
#include <filesystem>
#include <fstream>
#include <sstream>
#include <string>

namespace fs = std::filesystem;

std::string loadFile(const char *const name);
std::string loadFile(const std::string &name);

std::string loadFile(const char *const name) {
  fs::path filepath(fs::absolute(fs::path(name)));

  std::uintmax_t fsize;

  if (fs::exists(filepath)) {
    fsize = fs::file_size(filepath);
  } else {
    throw(std::invalid_argument("File not found: " + filepath.string()));
  }

  std::ifstream infile;
  infile.exceptions(std::ifstream::failbit | std::ifstream::badbit);
  try {
    infile.open(filepath.c_str(), std::ios::in | std::ifstream::binary);
  } catch (...) {
    std::throw_with_nested(std::runtime_error("Can't open input file " + filepath.string()));
  }

  std::string fileStr;

  try {
    fileStr.resize(fsize);
  } catch (...) {
    std::stringstream err;
    err << "Can't resize to " << fsize << " bytes";
    std::throw_with_nested(std::runtime_error(err.str()));
  }

  infile.read(fileStr.data(), fsize);
  infile.close();

  return fileStr;
}

std::string loadFile(const std::string &name) { return loadFile(name.c_str()); };
于 2019-11-06T20:24:17.700 回答
5

这样的事情应该不会太糟糕:

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

这里的优点是我们先做保留,这样我们就不必在读入内容时增加字符串。缺点是我们逐个字符地进行。更智能的版本可以抓取整个读取缓冲区,然后调用下溢。

于 2008-09-22T17:14:24.343 回答
3

您可以使用“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() ) );
于 2008-09-22T17:16:23.357 回答
0

对于性能,我没有发现比下面的代码更快的东西。

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;
}
于 2021-10-31T18:52:14.590 回答
0

您可以使用我开发的第一个C++ 库来执行此操作:

#include "rst/files/file_utils.h"

std::filesystem::path path = ...;  // Path to a file.
rst::StatusOr<std::string> content = rst::ReadFile(path);
if (content.err()) {
  // Handle error.
}

std::cout << *content << ", " << content->size() << std::endl;
于 2021-11-05T13:28:27.963 回答
0
#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();
    }
}
于 2022-02-10T13:59:13.403 回答
0

我知道这是一个非常古老的问题,有很多答案,但没有一个提到我认为最明显的方法。是的,我知道这是 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);
}

除了(在实践中)完全可移植之外,这似乎并不比其他一些解决方案更糟糕。当然,也可以抛出异常而不是立即退出。调整总是 0 的大小会初始化它,这让我非常恼火std::string,但它无济于事。

请注意,这只适用于为 C++17 及更高版本编写的代码。早期版本(应该)不允许编辑std::string::data()。如果使用早期版本,请考虑使用std::string_view或简单地复制原始缓冲区。

于 2021-10-10T08:01:34.833 回答
0
#include <iostream>
#include <fstream>
#include <string.h>
using namespace std;
main(){
    fstream file;
    //Open a file
    file.open("test.txt");
    string copy,temp;
    //While loop to store whole document in copy string
    //Temp reads a complete line
    //Loop stops until temp reads the last line of document
    while(getline(file,temp)){
        //add new line text in copy
        copy+=temp;
        //adds a new line
        copy+="\n";
    }
    //Display whole document
    cout<<copy;
    //close the document
    file.close();
}
于 2020-06-13T06:21:12.983 回答
0

基于 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()以获取文件的大小,并基于此构造输出字符串。

其次,我们避免了通过直接写入字符串来将文件的数据从 a 复制std::vector<char>到 a 。std::string

就性能而言,这应该是绝对最快的,提前分配合适大小的字符串,调用read()一次。作为一个有趣的事实,在 gcc 上使用ignore()andcountg()而不是ateandtellg()编译成几乎相同的东西,一点一点。

于 2020-05-04T01:34:37.030 回答
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;
}
于 2021-09-21T15:57:39.390 回答
0
#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);
于 2019-09-17T11:56:03.253 回答
-1

永远不要写入 std::string 的 const char * 缓冲区。永远不能!这样做是一个巨大的错误。

为 std::string 中的整个字符串保留()空间,从文件中读取合理大小的块到缓冲区中,然后 append()它。块的大小取决于您的输入文件大小。我很确定所有其他可移植和符合 STL 的机制都会做同样的事情(但可能看起来更漂亮)。

于 2008-09-22T17:05:37.297 回答
-2

我知道我迟到了,但现在(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;
}
于 2021-12-27T20:12:13.973 回答