Каков наилучший способ прочитать весь файл в std::string на С++?

Как я могу прочитать файл в 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)

с учетом вышеприведенного обсуждения.

Ответ 1

И самый быстрый (который я знаю, дисконтируя файлы с отображением памяти):

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

Ответ 2

См. этот ответ по аналогичному вопросу.

Для вашего удобства я отправляю решение 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);
}

Это решение привело к примерно на 20% быстрее времени выполнения, чем другие ответы, представленные здесь, при среднем средстве 100 пробегов против текста Moby Dick (1.3M). Неплохо для портативного решения на С++, мне бы хотелось увидеть результаты mmap'ing файла;)

Ответ 3

Самый короткий вариант: Live On Coliru

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

Для этого требуется заголовок <iterator>.

Были сообщения о том, что этот метод медленнее, чем предварительная выделение строки и использование std::istream::read. Однако на современном компиляторе с оптимизацией это уже не похоже, хотя относительная производительность различных методов, по-видимому, сильно зависит от компилятора.

Ответ 4

Используйте

#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, как было задано.

Ответ 5

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

Помните, что tellg() может возвращать -1 при ошибке. Если вы передаете результат tellg() в качестве параметра распределения, вы должны сначала проверить результат.

Пример проблемы:

...
std::streamsize size = file.tellg();
std::vector<char> buffer(size);
...

В приведенном выше примере, если tellg() встречается с ошибкой, он возвращает -1. Неявное кастинг между подписанным (т.е. Результатом tellg()) и unsigned (т.е. Arg для конструктора vector<char>) приведет к тому, что ваш вектор ошибочно выделит большое число байтов очень. (Возможно, 4294967295 байт или 4 ГБ.)

Модификация 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);
}

Ответ 6

Никогда не записывайте в буфер std::string const char *. Никогда! Это массовая ошибка.

Зарезервируйте() пространство для всей строки в std::string, прочитайте фрагменты из вашего файла разумного размера в буфер и добавьте() его. Насколько велики должны быть куски, зависит от размера вашего входного файла. Я уверен, что все другие портативные и STL-совместимые механизмы будут делать то же самое (но могут выглядеть красивее).

Ответ 7

Если у вас есть С++ 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;
}

Примечание: вам может потребоваться использовать <experimental/filesystem> и std::experimental::filesystem, если ваша стандартная библиотека еще не полностью поддерживает С++ 17. Вам также может потребоваться заменить result.data() на &result[0], если он не поддерживает данные non-const std:: basic_string.

Ответ 8

Что-то вроде этого не должно быть слишком плохо:

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

Преимущество в том, что мы делаем резерв в первую очередь, поэтому нам не нужно будет вырабатывать строку, когда мы читаем ее. Недостатком является то, что мы делаем это char на char. Умная версия может захватить весь прочитанный buf, а затем вызвать underflow.

Ответ 9

Вы можете использовать функцию "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() ) );

Ответ 10

Это решение добавляет проверку ошибок в метод 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();
}

Я добавляю этот ответ, потому что добавление проверки ошибок в исходный метод не так тривиально, как вы ожидали. В исходном методе используется оператор вставки строки string (str_stream << file_stream.rdbuf()). Проблема заключается в том, что это задает битбину stringstream, когда никакие символы не вставлены. Это может быть связано с ошибкой или может быть связано с тем, что файл пуст. Если вы проверяете ошибки, проверяя failbit, вы обнаружите ложный результат при чтении пустого файла. Как вы устраните законный отказ вставить любые символы и "отказ" вставить любые символы, потому что файл пуст?

Возможно, вы решили явно проверить пустой файл, но это больше кода и связанной проверки ошибок.

Проверка состояния отказа str_stream.fail() && !str_stream.eof() не работает, потому что операция вставки не устанавливает eofbit (в ostringstream или ifstream).

Итак, решение состоит в том, чтобы изменить операцию. Вместо того, чтобы использовать оператор вставки строки (<), используйте оператор извлечения потока ( → ), который устанавливает eofbit. Затем проверьте состояние отказа file_stream.fail() && !file_stream.eof().

Важно отметить, что когда file_stream >> str_stream.rdbuf() встречается с законным сбоем, он никогда не должен устанавливать eofbit (согласно моему пониманию спецификации). Это означает, что вышеуказанная проверка достаточна для обнаружения законных сбоев.

Ответ 11

Что делать, если вы кладете 11K файл, тогда вам нужно сделать это в серии кусков, поэтому вам нужно использовать что-то вроде std::vector, чтобы разделить его на большие куски строк.