Как мне прочитать весь файл в 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 ++ теперь гарантированы непрерывные строки, можно написать следующее (≥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;
}
Были сообщения, что этот метод медленнее, чем предварительное выделение строки и использование std::istream::read. Однако в современном компиляторе с включенной оптимизацией этого больше не происходит, хотя относительная производительность различных методов, по-видимому, сильно зависит от компилятора.
Это решение привело к увеличению времени выполнения примерно на 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.
или что-то очень близкое. У меня нет открытой ссылки на stdlib, чтобы перепроверить себя.
Да, я понимаю, что написал функцию slurp не так, как просили.
Ответ 6 · рейтинг 17 · Rick Ramstetter
У меня недостаточно репутации, чтобы напрямую комментировать ответы с помощью tellg().
Имейте в виду, что tellg() может вернуть -1 в случае ошибки. Если вы передаете результат tellg() в качестве параметра распределения, вы должны сначала проверить результат.
В приведенном выше примере, если tellg() обнаруживает ошибку, он вернет -1. Неявное преобразование между подписанным (т.е. результатом tellg()) и беззнаковым (т.е. аргументом конструктора vector<char>) приведет к тому, что ваш вектор ошибочно выделит очень большое количество байтов. (Вероятно, 4294967295 байт или 4 ГБ.)
Изменение ответа paxos1977 для учета вышеуказанного:
Я добавляю этот ответ, потому что добавление проверки ошибок к исходному методу не так тривиально, как вы ожидали. В исходном методе используется оператор вставки строкового потока (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 (в соответствии с моим пониманием спецификации). Это означает, что вышеуказанной проверки достаточно для обнаружения законных сбоев.
Преимущество здесь в том, что мы сначала делаем резерв, поэтому нам не придется увеличивать строку по мере чтения. Недостатком является то, что мы делаем это char за char. Более умная версия могла бы захватить весь буфер чтения и затем вызвать недополнение.
Ответ 9 · рейтинг 5 · David G
Вот версия, использующая новую библиотеку файловой системы с достаточно надежной проверкой ошибок:
Поскольку это похоже на широко используемую утилиту, мой подход заключался бы в поиске и предпочтении уже доступных библиотек решениям, сделанным вручную, особенно если библиотеки boost уже связаны (флаги компоновщика -lboost_system -lboost_filesystem) в вашем проекте. Здесь (и более старые версии Boost) boost предоставляет утилита load_string_file:
Преимущество этой функции заключается в том, что эта функция не ищет весь файл для определения размера, а использует внутри stat (). Однако, как, возможно, незначительный недостаток, можно легко сделать вывод при просмотре исходного кода: строка излишне изменяется с использованием символа '\0', который перезаписывается содержимым файла.
Ответ 11 · рейтинг 3 · Martin Cote
Вы можете использовать функцию std :: getline и указать eof в качестве разделителя. Однако полученный код немного неясен: