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

时间:2020-03-06 14:33:02  来源:igfitidea点击:

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

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

一种方法是统计文件大小,将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 <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函数。

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

在std :: string中为整个字符串保留()空间,从文件中将适当大小的块读取到缓冲区中,然后执行append()。块必须有多大取决于输入文件的大小。我敢肯定,所有其他可移植且符合STL的机制都将执行相同的操作(但可能看起来更漂亮)。

最短的变体:" Live on Coliru"

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

它需要头文件" <iterator>"。

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

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

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

这样做的好处是我们首先进行了保留,因此在读入内容时不必增加字符串。缺点是我们逐个字符地进行处理。一个更聪明的版本可以获取整个读取的buf,然后调用下溢。

我们可以使用'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() ) );

最快(据我所知,对内存映射文件进行打折):

std::string str(static_cast<std::stringstream const&>(std::stringstream() << in.rdbuf()).str());

这需要为字符串流添加头文件<sstream>。
(必须使用" static_cast",因为" operator <<"会返回一个普通的旧" ostream&",但是我们知道实际上它是一个" stringstream&",因此强制转换是安全的。)

分成多行,将临时变量移动到变量中,我们得到了更具可读性的代码:

std::string slurp(std::ifstream& in) {
    std::stringstream sstr;
    sstr << in.rdbuf();
    return sstr.str();
}

或者,再次在一行中:

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

看到类似问题的答案。

为了方便,我重新发布了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文件的结果;)