将文件读入std :: string的最有效方法是什么?

时间:2012-01-05 02:22:14

标签: c++ string file-io

我目前正在执行此操作,最后转换为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;
}

4 个答案:

答案 0 :(得分:4)

你可以试试这个:

#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();只需确保在某处某些类型的错误检查。

无法保证 效率最高;你可能无法击败<cstdio>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);

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


编辑:第二版的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>());

(注意附加的括号以获得正确的解析。)

答案 1 :(得分:4)

作为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";
}

对于我自己实现的IOStream来说,这当然是快速的,但实际上要快速实现它需要很多技巧。首先,它需要优化算法来处理分段序列:流可以看作是一系列输入缓冲区。我不知道任何STL实现始终如一地进行此优化。奇怪的使用std::skipws只是为了引用刚刚创建的流:std::istreambuf_iterator<char>需要一个临时文件流不会绑定的引用。

由于这可能不是最快的方法,我倾向于将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():在这种情况下,您可以使用{{1}对于从未出现的字符(eof()char_typeint_type,请指向eof()的相应成员。反过来,成员版本无法使用,因为您需要提前知道文件中有多少个字符。

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

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

std::string

至少使用适当选择的缓冲区,我希望版本相当快。哪个版本最快,肯定取决于系统,正在使用的标准C ++库,以及其他一些因素,即您想要衡量性能。

答案 2 :(得分:1)

具有讽刺意味的是,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;
}

答案 3 :(得分:1)

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

string buffer;

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

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

fin.close();
}

这的效率显然取决于getline算法内部的内容,因此您可以查看标准库中的代码,看看它是如何工作的。