【问题标题】:What's the most efficient way to read a file into a std::string?将文件读入 std::string 的最有效方法是什么?
【发布时间】:2012-01-05 02:22:14
【问题描述】:

我目前正在这样做,最后转换为 std::string 需要 98% 的执行时间。一定有更好的办法!

std::string
file2string(std::string filename)
{
    std::ifstream file(filename.c_str());
    if(!file.is_open()){
        // If they passed a bad file name, or one we have no read access to,
        // we pass back an empty string.
        return "";
    }
    // find out how much data there is
    file.seekg(0,std::ios::end);
    std::streampos length = file.tellg();
    file.seekg(0,std::ios::beg);
    // Get a vector that size and
    std::vector<char> buf(length);
    // Fill the buffer with the size
    file.read(&buf[0],length);
    file.close();
    // return buffer as string
    std::string s(buf.begin(),buf.end());
    return s;
}

【问题讨论】:

标签: c++ string file-io


【解决方案1】:

作为 C++ 迭代器抽象和算法的忠实拥护者,我希望以下方法能够快速将文件(或任何其他输入流)读入 std::string(然后打印内容):

#include <algorithm>
#include <fstream>
#include <iostream>
#include <iterator>
#include <string>

int main()
{
    std::string s(std::istreambuf_iterator<char>(std::ifstream("file")
                                                 >> std::skipws),
                  std::istreambuf_iterator<char>());
    std::cout << "file='" << s << "'\n";
}

这对于我自己的 IOStreams 实现来说确实很快,但实际上要快速实现它需要很多技巧。首先,它需要优化算法来处理分段序列:一个流可以看作是一个输入缓冲区序列。我不知道有任何 STL 实现一直在做这种优化。 std::skipws 的奇怪用法只是获取对刚刚创建的流的引用:std::istreambuf_iterator&lt;char&gt; 期望临时文件流不会绑定到的引用。

由于这可能不是最快的方法,我倾向于使用带有特定“换行符”字符的std::getline(),即文件中没有的字符:

std::string s;
// optionally reserve space although I wouldn't be too fuzzed about the
// reallocations because the reads probably dominate the performances
std::getline(std::ifstream("file") >> std::skipws, s, 0);

这假定文件不包含空字符。任何其他角色也可以。不幸的是,std::getline()char_type 用作分隔参数,而不是 int_type 成员 std::istream::getline() 用作分隔符:在这种情况下,您可以将 eof() 用于从未出现的字符(@ 987654332@、int_typeeof()分别指char_traits&lt;char&gt;的成员)。反过来,会员版本不能使用,因为您需要提前知道文件中有多少个字符。

顺便说一句,我看到一些尝试使用 seek 来确定文件的大小。这注定不会很好地工作。问题是在std::ifstream(好吧,实际上是在std::filebuf)中完成的代码转换可以创建与文件中的字节数不同的字符数。诚然,在使用默认 C 语言环境时情况并非如此,并且可以检测到这不会进行任何转换。否则,流的最佳选择是遍历文件并确定正在生成的字符数。我实际上认为这是当代码转换可能会发生一些有趣的事情时需要做的,尽管我认为它实际上并没有完成。但是,没有一个示例明确设置 C 语言环境,例如使用std::locale::global(std::locale("C"));。即使这样,也需要以std::ios_base::binary 模式打开文件,否则读取时行尾序列可能会被单个字符替换。诚然,这只会使结果更短,永远不会更长。

使用从std::streambuf* 提取的其他方法(即涉及rdbuf() 的方法)都要求在某个时间点复制生成的内容。鉴于文件实际上可能非常大,这可能不是一个选项。然而,如果没有副本,这很可能是最快的方法。为避免复制,可以创建一个简单的自定义流缓冲区,它将对 std::string 的引用作为构造函数参数并直接附加到此 std::string

#include <fstream>
#include <iostream>
#include <string>

class custombuf:
    public std::streambuf
{
public:
    custombuf(std::string& target): target_(target) {
        this->setp(this->buffer_, this->buffer_ + bufsize - 1);
    }

private:
    std::string& target_;
    enum { bufsize = 8192 };
    char buffer_[bufsize];
    int overflow(int c) {
        if (!traits_type::eq_int_type(c, traits_type::eof()))
        {
            *this->pptr() = traits_type::to_char_type(c);
            this->pbump(1);
        }
        this->target_.append(this->pbase(), this->pptr() - this->pbase());
        this->setp(this->buffer_, this->buffer_ + bufsize - 1);
        return traits_type::not_eof(c);
    }
    int sync() { this->overflow(traits_type::eof()); return 0; }
};

int main()
{
    std::string s;
    custombuf   sbuf(s);
    if (std::ostream(&sbuf)
        << std::ifstream("readfile.cpp").rdbuf()
        << std::flush) {
        std::cout << "file='" << s << "'\n";
    }
    else {
        std::cout << "failed to read file\n";
    }
}

At least with a suitably chosen buffer I would expect the version to be the fairly fast.哪个版本最快肯定取决于系统、正在使用的标准 C++ 库,以及可能还有许多其他因素,即您想要衡量性能。

【讨论】:

  • @highDef4k:假设您使用std::ios::binary 标志打开文件:是的。
【解决方案2】:

你可以试试这个:

#include <fstream>
#include <sstream>
#include <string>

int main()
{
  std::ostringstream oss;
  std::string s;
  std::string filename = get_file_name();

  if (oss << std::ifstream(filename, std::ios::binary).rdbuf())
  {
    s = oss.str();
  }
  else
  {
    // error
  }

  // now s contains your file     
}

如果你愿意,也可以直接使用oss.str();只需确保您在某处进行了某种类型的错误检查。

不保证它是效率的;你可能无法击败&lt;cstdio&gt;fread。正如@Benjamin 指出的那样,字符串流仅通过副本公开数据,因此您可以直接读入目标字符串:

#include <string>
#include <cstdio>

std::FILE * fp = std::fopen("file.bin", "rb");
std::fseek(fp, 0L, SEEK_END);
unsigned int fsize = std::ftell(fp);
std::rewind(fp);

std::string s(fsize, 0);
if (fsize != std::fread(static_cast<void*>(&s[0]), 1, fsize, fp))
{
   // error
}

std::fclose(fp);

(您可能希望将RAII wrapper 用于FILE*。)


编辑:第二版的fstream-analogue是这样的:

#include <string>
#include <fstream>

std::ifstream infile("file.bin", std::ios::binary);
infile.seekg(0, std::ios::end);
unsigned int fsize = infile.tellg();
infile.seekg(0, std::ios::beg);

std::string s(fsize, 0);

if (!infile.read(&s[0], fsize))
{
   // error
}

编辑:另一个版本,使用streambuf-iterators:

std::ifstream thefile(filename, std::ios::binary);
std::string s((std::istreambuf_iterator<char>(thefile)), std::istreambuf_iterator<char>());

(注意额外的括号以获得正确的解析。)

【讨论】:

  • 我很确定这一举动不会给你带来任何好处。 ostringstream::str() 按值返回。
  • @BenjaminLindley:哦,好点。没关系,直接使用oss.str()
  • 我创建了一个框架,将这些中的每一个作为函数调用 1000 次,以读取 1.3M jpeg。 Kerrek 的第一个是 19 秒,第二个是 6 秒。我的用了 14 秒,大卫用了 2 分 21 秒。 C++ 能否利用标准模板库的元素高效地进行文件 I/O?
  • @Patrick:如果有什么安慰的话,C 库是 C++ 标准库的一部分,所以不要羞于使用&lt;cstdio&gt;。但是请让我使用&lt;fstream&gt; 发布另一个版本。请继续关注。
【解决方案3】:

具有讽刺意味的是,example for string::reserve 正在将文件读入字符串。您不想将文件读入一个缓冲区,然后必须分配/复制到另一个缓冲区。

示例代码如下:

// string::reserve
#include <iostream>
#include <fstream>
#include <string>
using namespace std;

int main ()
{
  string str;
  size_t filesize;

  ifstream file ("test.txt",ios::in|ios::ate);
  filesize=file.tellg();

  str.reserve(filesize); // allocate space in the string

  file.seekg(0);
  for (char c; file.get(c); )
  {
    str += c;
  }
  cout << str;
  return 0;
}

【讨论】:

  • 我同意。我不确定他们为什么选择这样做。重要的一点是str.reserve 只进行一次分配,然后读入字符串。
  • 有一个正确的例子也很重要,不是吗?希望您不介意编辑。
  • 我制作了一个框架,将这些中的每一个作为函数调用 1000 次,以读取 1.3M 的 jpeg。 Kerrek 的第一个是 19 秒,第二个是 6 秒。我的用了 14 秒,大卫用了 2 分 21 秒。 C++ 能否利用标准模板库的元素高效地进行文件 I/O?
【解决方案4】:

我不知道它的效率如何,但这是一种简单(易于阅读)的方法,只需将 EOF 设置为分隔符:

string buffer;

ifstream fin;
fin.open("filename.txt");

if(fin.is_open()) {
    getline(fin,buffer,'\x1A');

fin.close();
}

这显然取决于 getline 算法内部发生了什么,因此您可以查看标准库中的代码以了解其工作原理。

【讨论】:

    猜你喜欢
    • 2016-08-20
    • 1970-01-01
    • 1970-01-01
    • 1970-01-01
    • 1970-01-01
    • 1970-01-01
    • 1970-01-01
    • 2019-08-31
    • 2021-05-29
    相关资源
    最近更新 更多