Bezdepbonusu7Программирование: статьи и ответы

Вопрос

Как мне прочитать весь файл в std :: string в C ++?

Автор вопроса: CommunityОтветов: 12

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

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

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

Полностью правильные, совместимые со стандартами и переносимые решения могут быть созданы с использованием rdbuf() std::ifstream в std::ostringstream, а оттуда в std::string. Однако это может скопировать строковые данные и / или без необходимости перераспределить память.

  • Достаточно ли умны все соответствующие реализации стандартных библиотек, чтобы избежать ненужных накладных расходов?
  • Есть другой способ сделать это?
  • Я пропустил какую-то скрытую функцию Boost, которая уже обеспечивает желаемую функциональность?
void slurp(std::string& data, bool is_binary)
  • Обратите внимание, что у вас все еще есть недоопределенные параметры. Например, какова кодировка символов в файле? Будете ли вы пытаться автоматически определять (что работает только в некоторых конкретных случаях)? Будете ли вы уважать, например, Заголовки XML сообщают вам кодировку файла? Также нет таких вещей, как текстовый режим или двоичный режим - вы думаете о FTP? Jason Cohen 22.09.2008
  • Текстовый и двоичный режимы - это специальные хаки для MSDOS и Windows, которые пытаются обойти тот факт, что новые строки представлены двумя символами в Windows (CR / LF). В текстовом режиме они обрабатываются как один символ ('\ n'). Ferruccio 22.09.2008
  • Обычно такие вещи лечатся подпрограммами, которые разбивают строки на строки, а не подпрограммами, считывающими данные из файлов. То есть в каждой среде, в которой я программировал, есть какие-то функции readAsLines () или breakIntoLines (), которые разбираются в таких вещах. Jason Cohen 22.09.2008
  • Хотя это не (совсем) точная копия, это тесно связано с: как предварительно выделить память для объекта std :: string? (который, в отличие от приведенного выше утверждения Конрада, включал код для этого, читая файл прямо в место назначения, без дополнительной копии). Jerry Coffin 21.09.2012
  • прилегающий не требуется стандартом - да, окольным путем. Как только вы используете op [] в строке, она должна быть объединена в непрерывный записываемый буфер, поэтому запись в & str [0] гарантированно безопасна, если сначала вы .resize () достаточно большой. А в C ++ 11 строка просто всегда непрерывна. Tino Didriksen 19.07.2013
  • Связанная ссылка: Как читать файл в C ++? - тесты и обсуждение различных подходов. И да, rdbuf (тот, что в принятом ответе) не самый быстрый, read. legends2k 27.11.2014

c++file-iostring

Ответы

Ответ 1 · рейтинг 151 · Konrad Rudolph

Один из способов - сбросить буфер потока в отдельный поток памяти, а затем преобразовать его в std::string:

std::string slurp(std::ifstream& in) {
    std::ostringstream sstr;
    sstr << in.rdbuf();
    return sstr.str();
}

Это красиво лаконично. Однако, как указано в вопросе, это выполняет дублирующую копию, и, к сожалению, принципиально нет способа удалить эту копию.

К сожалению, единственное реальное решение, позволяющее избежать дублирования копий, - это выполнять чтение вручную в цикле. Поскольку в C ++ теперь гарантированы непрерывные строки, можно написать следующее (≥C ++ 14):

auto read_file(std::string_view path) -> std::string {
    constexpr auto read_size = std::size_t{4096};
    auto stream = std::ifstream{path.data()};
    stream.exceptions(std::ios_base::badbit);

    auto out = std::string{};
    auto buf = std::string(read_size, '\0');
    while (stream.read(& buf[0], read_size)) {
        out.append(buf, 0, stream.gcount());
    }
    out.append(buf, 0, stream.gcount());
    return out;
}

Ответ 2 · рейтинг 61 · Konrad Rudolph

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

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

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

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

Ответ 3 · рейтинг 54 · paxos1977

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

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

Ответ 4 · рейтинг 30 · Gabriel Majeri

Если у вас 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, std::ios::in | std::ios::binary);

    // Obtain the size of the file.
    const auto sz = fs::file_size(path);

    // Create a buffer.
    std::string result(sz, '\0');

    // 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], если он не поддерживает non-const std :: данные basic_string.

Ответ 5 · рейтинг 24 · Ben Collins

Использовать

#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 не так, как просили.

Ответ 6 · рейтинг 17 · Rick Ramstetter

У меня недостаточно репутации, чтобы напрямую комментировать ответы с помощью 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);
}

Ответ 7 · рейтинг 7 · tgnottingham

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

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

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

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

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

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

Ответ 8 · рейтинг 5 · Matt Price

Что-то вроде этого не должно быть так уж плохо:

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

Ответ 9 · рейтинг 5 · David G

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

#include <cstdint>
#include <exception>
#include <filesystem>
#include <fstream>
#include <sstream>
#include <string>

namespace fs = std::filesystem;

std::string loadFile(const char *const name);
std::string loadFile(const std::string &name);

std::string loadFile(const char *const name) {
  fs::path filepath(fs::absolute(fs::path(name)));

  std::uintmax_t fsize;

  if (fs::exists(filepath)) {
    fsize = fs::file_size(filepath);
  } else {
    throw(std::invalid_argument("File not found: " + filepath.string()));
  }

  std::ifstream infile;
  infile.exceptions(std::ifstream::failbit | std::ifstream::badbit);
  try {
    infile.open(filepath.c_str(), std::ios::in | std::ifstream::binary);
  } catch (...) {
    std::throw_with_nested(std::runtime_error("Can't open input file " + filepath.string()));
  }

  std::string fileStr;

  try {
    fileStr.resize(fsize);
  } catch (...) {
    std::stringstream err;
    err << "Can't resize to " << fsize << " bytes";
    std::throw_with_nested(std::runtime_error(err.str()));
  }

  infile.read(fileStr.data(), fsize);
  infile.close();

  return fileStr;
}

std::string loadFile(const std::string &name) { return loadFile(name.c_str()); };

Ответ 10 · рейтинг 5 · b.g.

Поскольку это похоже на широко используемую утилиту, мой подход заключался бы в поиске и предпочтении уже доступных библиотек решениям, сделанным вручную, особенно если библиотеки boost уже связаны (флаги компоновщика -lboost_system -lboost_filesystem) в вашем проекте. Здесь (и более старые версии Boost) boost предоставляет утилита load_string_file:

#include <iostream>
#include <string>
#include <boost/filesystem/string_file.hpp>

int main() {
    std::string result;
    boost::filesystem::load_string_file("aFileName.xyz", result);
    std::cout << result.size() << std::endl;
}

Преимущество этой функции заключается в том, что эта функция не ищет весь файл для определения размера, а использует внутри stat (). Однако, как, возможно, незначительный недостаток, можно легко сделать вывод при просмотре исходного кода: строка излишне изменяется с использованием символа '\0', который перезаписывается содержимым файла.

Ответ 11 · рейтинг 3 · Martin Cote

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

Ответ 12 · Paul Sumpner

#include <string>
#include <sstream>

using namespace std;

string GetStreamAsString(const istream& in)
{
    stringstream out;
    out << in.rdbuf();
    return out.str();
}

string GetFileAsString(static string& filePath)
{
    ifstream stream;
    try
    {
        // Set to throw on failure
        stream.exceptions(fstream::failbit | fstream::badbit);
        stream.open(filePath);
    }
    catch (system_error& error)
    {
        cerr << "Failed to open '" << filePath << "'\n" << error.code().message() << endl;
        return "Open fail";
    }

    return GetStreamAsString(stream);
}

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

const string logAsString = GetFileAsString(logFilePath);

Перевод вопроса и ответов сообщества Stack Overflow, лицензия CC BY-SA. Оригинал на Stack Overflow.

Похожие материалы