在C++中将整个文件读入std :: string的最佳方法是什么?

155 c++ string file-io

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

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

实现此目的的一种方法是统计文件大小,调整大小std::stringfread()进入std::string's const_cast<char*>()' data().这要求std::string数据是连续的,这是标准不需要的,但似乎是所有已知实现的情况.更糟糕的是,如果在文本模式下读取文件,则其std::string大小可能与文件大小不同.

一个完全正确的,符合标准的和便携式解决方案,可以构建使用std::ifstreamrdbuf()进入std::ostringstream,并从那里进入std::string.但是,这可能会复制字符串数据和/或不必要地重新分配内存.所有相关的标准库实现是否足够智能以避免所有不必要的开销?还有另一种方法吗?我是否错过了一些已经提供所需功能的隐藏Boost功能?

请显示您的建议如何实施.

void slurp(std::string& data, bool is_binary)
Run Code Online (Sandbox Code Playgroud)

考虑到上面的讨论.

Kon*_*lph 129

并且最快(我知道,折扣内存映射文件):

std::string str(static_cast<std::stringstream const&>(std::stringstream() << in.rdbuf()).str());
Run Code Online (Sandbox Code Playgroud)

这需要<sstream>字符串流的附加标头.(这static_cast是必要的,因为operator <<返回一个普通的老,ostream&但我们知道,实际上它是一个stringstream&所以演员是安全的.)

拆分成多行,将临时值移动到变量中,我们得到一个更易读的代码:

std::string slurp(std::ifstream& in) {
    std::stringstream sstr;
    sstr << in.rdbuf();
    return sstr.str();
}
Run Code Online (Sandbox Code Playgroud)

或者,再次在一行中:

std::string slurp(std::ifstream& in) {
    return static_cast<std::stringstream const&>(std::stringstream() << in.rdbuf()).str();
}
Run Code Online (Sandbox Code Playgroud)

  • @DevSolar嗯,更易读的版本缩短了约30%,缺少演员阵容,而且相同.因此,我的问题是:"使它成为一个问题的重点是什么?" (37认同)
  • 使它成为一个oneliner有什么意义?我总是选择清晰的代码.作为一个自称为VB.Net的爱好者(IIRC),我认为你应该理解这种情绪? (17认同)
  • 注意:此方法将文件读入stringstream的缓冲区,然后将整个缓冲区复制到`string`中.即需要两倍于其他一些选项的内存.(无法移动缓冲区).对于大文件,这将是一个重要的惩罚,甚至可能导致分配失败. (13认同)
  • @DanNissenbaum你有点困惑.简洁性在编程中确实很重要,但实现它的正确方法是将问题分解为部分并将它们封装到独立的单元(函数,类等)中.添加功能不会减损简洁; 恰恰相反. (9认同)
  • @sehe:我希望任何能够中途胜任的C++编码人员能够轻松理解单行代码.与周围的其他东西相比,这是相当温和的. (5认同)
  • 我知道这是非常古老的,但我只是对几种方法进行了一些分析,我发现获取文件大小并将`in.read`调用到预分配到正确大小的缓冲区比这快得多.大约10倍.我正在使用VS2012并使用100mb文件进行测试. (2认同)
  • 只是想补充一点,对于学习C++的人来说,乍一看这很难理解. (2认同)
  • @John这就是为什么你把它放到正常的功能.对于初学者来说,大多数重要的代码都很难理解,如果这是反对使用这些代码的论据,我们就永远不会完成任何工作. (2认同)
  • @Ayxan 使用 `dynamic_cast` 只有在您不知道转换是否成功并测试返回值(或捕获潜在的 `bad_cast`)时才真正有意义。然而,我们知道演员在这里成功了,所以没有必要对冲我们的赌注。理想情况下,我们会使用一个*仅*执行向下转换的强制转换,同时断言强制转换会成功。唉,C++ 中不存在这样的强制转换。 (2认同)
  • 如果文件不存在,`read_file` 不会报告任何内容? (2认同)

pax*_*977 51

在类似的问题上看到这个答案.

为了您的方便,我正在重新发布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);
}
Run Code Online (Sandbox Code Playgroud)

与针对Moby Dick(1.3M)文本的平均100次运行相比,此解决方案的执行时间比此处提供的其他答案快20%.对于便携式C++解决方案来说还不错,我希望看到mmap的文件结果;)

  • 直到今天,我从未目睹tellg()报告非文件大小的结果.花了我几个小时才找到bug的来源.请不要使用tellg()来获取文件大小.http://stackoverflow.com/questions/22984956/tellg-function-give-wrong-size-of-file/22986486#22986486 (7认同)
  • 相关:各种方法的时间性能比较:[在C++中一次读取整个文件](http://insanecoding.blogspot.ru/2011/11/reading-in-entire-file-at-once-in-c的.html) (3认同)

Kon*_*lph 45

最短的变种: Live On Coliru

std::string str(std::istreambuf_iterator<char>{ifs}, {});
Run Code Online (Sandbox Code Playgroud)

它需要标题<iterator>.

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

  • 你能解决这个问题吗?它有多高效,它一次读取一个文件,无论如何要预先分配搅拌记忆? (7认同)

Ben*_*ins 17

使用

#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;
}
Run Code Online (Sandbox Code Playgroud)

或者非常接近的东西.我没有打开stdlib引用来仔细检查自己.

是的,我知道我没有slurp按要求编写该功能.

  • 为什么while循环? (4认同)
  • while循环不应该是"while(input >> sstr.rdbuf());" ? (3认同)
  • 同意。当“operator&gt;&gt;”读入“std::basic_streambuf”时,它将消耗输入流(剩下的部分),因此循环是不必要的。 (2认同)

Ric*_*ter 12

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

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

问题的一个例子:

...
std::streamsize size = file.tellg();
std::vector<char> buffer(size);
...
Run Code Online (Sandbox Code Playgroud)

在上面的例子中,如果tellg()遇到错误,它将返回-1.签署(IE的结果之间的隐式转换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);
}
Run Code Online (Sandbox Code Playgroud)


Gab*_*l M 7

如果你有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 };

    // Obtain the size of the file.
    const auto sz = fs::file_size(path);

    // Create a buffer.
    std::string result(sz, ' ');

    // Read the whole file into the buffer.
    f.read(result.data(), sz);

    return result;
}
Run Code Online (Sandbox Code Playgroud)

注意:您可能需要使用<experimental/filesystem>,std::experimental::filesystem如果您的标准库尚未完全支持C++ 17.您可能还需要更换result.data()&result[0]如果它不支持非const的std :: basic_string的数据.

  • 使用一个 API 打开文件并使用另一个 API 获取其大小似乎要求不一致和竞争条件。 (7认同)
  • 这可能会导致未定义的行为;在某些操作系统上,以文本模式打开文件会产生与磁盘文件不同的流。 (6认同)

tgn*_*ham 7

此解决方案向基于 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();
}
Run Code Online (Sandbox Code Playgroud)

我添加这个答案是因为向原始方法添加错误检查并不像您期望的那么简单。原始方法使用 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(根据我对规范的理解)。这意味着上述检查足以检测合法故障。


Mat*_*ice 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>());
}
Run Code Online (Sandbox Code Playgroud)

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


Dav*_*d G 5

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

#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()); };
Run Code Online (Sandbox Code Playgroud)


b.g*_*.g. 5

由于这似乎是一个广泛使用的实用程序,因此我的方法是搜索并优先选择已有的库而不是手工制作的解决方案,特别是如果您的项目中已经链接了 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;
}
Run Code Online (Sandbox Code Playgroud)

作为一个优势,此函数不会寻找整个文件来确定大小,而是在内部使用 stat()。然而,作为一个可能可以忽略不计的缺点,通过检查源代码可以很容易地推断出:字符串被不必要地调整'\0'为由文件内容重写的字符。