Каков наилучший способ прочитать весь файл в std :: string в C ++? - PullRequest
144 голосов
/ 22 сентября 2008

Как мне прочитать файл в std::string, т.е. прочитать весь файл сразу?

Текстовый или двоичный режим должен быть указан вызывающим абонентом. Решение должно быть совместимым со стандартами, переносимым и эффективным. Он не должен без необходимости копировать данные строки и должен избегать перераспределения памяти при чтении строки.

Один из способов сделать это - указать размер файла, изменить размеры std::string и fread() до std::string 's const_cast<char*>()' ed data(). Это требует, чтобы данные std::string были смежными, что не требуется стандартом, но, похоже, имеет место для всех известных реализаций. Что еще хуже, если файл читается в текстовом режиме, размер std::string может не совпадать с размером файла.

Полностью правильные, совместимые со стандартами и переносимые решения могут быть созданы с использованием std::ifstream s rdbuf() в std::ostringstream и оттуда в std::string. Однако это может скопировать строковые данные и / или излишне перераспределить память. Все ли соответствующие реализации стандартных библиотек достаточно умны, чтобы избежать ненужных накладных расходов? Есть ли другой способ сделать это? Я пропустил какую-то скрытую функцию Boost, которая уже обеспечивает желаемую функциональность?

Пожалуйста, покажите, как это реализовать.

void slurp(std::string& data, bool is_binary)

с учетом вышеизложенного.

Ответы [ 10 ]

118 голосов
/ 22 сентября 2008

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

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();
}
46 голосов
/ 08 февраля 2009

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

Для вашего удобства я публикую решение 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 прогонов против текста Моби Дика (1,3M). Неплохо для портативного решения C ++, я хотел бы увидеть результаты mmap'ing файла;)

41 голосов
/ 22 сентября 2008

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

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

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

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

16 голосов
/ 22 сентября 2008

Использование

#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, как было предложено.

10 голосов
/ 25 марта 2017

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

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

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

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

В приведенном выше примере, если tellg() обнаружит ошибку, он вернет -1. Неявное приведение между подписанным (т.е. результатом tellg()) и неподписанным (то есть аргументом конструктору 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);
}
5 голосов
/ 01 декабря 2016

Если у вас есть C ++ 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, если ваша стандартная библиотека еще не полностью поддерживает C ++ 17. Вам также может потребоваться заменить result.data() на &result[0], если он не поддерживает неконстантные данные std :: basic_string .

4 голосов
/ 22 сентября 2008

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

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

3 голосов
/ 26 марта 2017

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

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

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

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

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

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

3 голосов
/ 22 сентября 2008

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

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. Более разумная версия может получить весь прочитанный буфер и затем вызвать underflow.

2 голосов
/ 22 сентября 2008

Вы можете использовать функцию '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() ) );
Добро пожаловать на сайт PullRequest, где вы можете задавать вопросы и получать ответы от других членов сообщества.
...