Какой самый эффективный способ прочитать файл в std :: string? - PullRequest
2 голосов
/ 05 января 2012

В настоящее время я делаю это, и преобразование в 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 ]

4 голосов
/ 05 января 2012

Вы можете попробовать это:

#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);

(Вы можете использовать RAII-оболочку для FILE*.)


Редактировать: Аналог fstream второй версии выглядит следующим образом:

#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-итераторы:

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

(Запомните дополнительные скобки, чтобы получить правильный синтаксический анализ.)

4 голосов
/ 05 января 2012

Будучи большим поклонником абстракций итераторов 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<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() принимает за разделитель: в этом случае вы можете использовать eof() для символа, который никогда не встречается (char_type, int_type и eof() относятся к соответствующему элементу char_traits<char>).Версия участника, в свою очередь, не может быть использована, потому что вам нужно знать заранее, сколько символов в файле.

Кстати, я видел несколько попыток использовать поиск для определения размерафайл.Это не должно работать слишком хорошо.Проблема в том, что преобразование кода, выполненное в 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";
    }
}

По крайней мере снадлежащим образом выбранный буфер, я ожидаю, что версия будет довольно быстрой.Какая версия является самой быстрой, безусловно, будет зависеть от системы, используемой стандартной библиотеки C ++ и, возможно, ряда других факторов, например, вы хотите измерить производительность.

1 голос
/ 12 августа 2012

Я не знаю, насколько это эффективно, но вот простой (для чтения) способ, просто установив EOF в качестве разделителя:

string buffer;

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

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

fin.close();
}

Эффективность этого, очевидно, зависит от того, что происходит внутри алгоритма getline, так что вы можете взглянуть на код в стандартных библиотеках, чтобы увидеть, как он работает.

1 голос
/ 05 января 2012

По иронии судьбы, пример для 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;
}
Добро пожаловать на сайт PullRequest, где вы можете задавать вопросы и получать ответы от других членов сообщества.
...