Как мне прочитать файл в std::string, т.е. прочитать весь файл сразу?
Текстовый или двоичный режим должен быть указан вызывающим. Решение должно быть совместимым со стандартами, портативным и эффективным. Он не должен без необходимости копировать данные строки и должен избегать перераспределения памяти при чтении строки.
Один из способов сделать это - установить размер файла, изменить размер std::string и fread() на std::string с const_cast<char*>(). Это требует, чтобы данные data() были непрерывными, что не требуется стандартом, но, похоже, так обстоит дело для всех известных реализаций. Что еще хуже, если файл читается в текстовом режиме, размер std::string может не совпадать с размером файла.
Полностью правильные, соответствующие стандартам и портативные решения могут быть сконструированы с использованием std::stringstd::ifstream в rdbuf(), а оттуда в std::ostringstream. Однако это может скопировать строковые данные и / или без необходимости перераспределить память.
void slurp(std::string& data, bool is_binary)
«смежные по стандарту не требуются» - да, окольным путем. Как только вы используете op [] в строке, она должна быть объединена в непрерывный записываемый буфер, поэтому запись в & str [0] гарантированно безопасна, если сначала вы .resize () достаточно большой. А в C++ 11 строка просто всегда непрерывна.
Связанная ссылка: Как читать файл на C++? - тесты и обсуждение различных подходов. И да, rdbuf (тот, что в принятом ответе) не самый быстрый, read.
Обратите внимание, что у вас все еще есть недоопределенные параметры. Например, какова кодировка символов в файле? Будете ли вы пытаться автоматически определять (что работает только в некоторых конкретных случаях)? Будете ли вы уважать, например Заголовки XML сообщают вам кодировку файла? Также не существует таких понятий, как «текстовый режим» или «двоичный режим» - вы думаете о FTP?
Текстовый и двоичный режимы - это специальные хаки для MSDOS и Windows, которые пытаются обойти тот факт, что новые строки представлены двумя символами в Windows (CR / LF). В текстовом режиме они обрабатываются как один символ ('\ n').
Обычно такие вещи обрабатываются подпрограммами, которые разбивают строки на строки, а не подпрограммами, считывающими данные из файлов. То есть в каждой среде, в которой я программировал, есть какие-то функции readAsLines () или breakIntoLines (), которые разбираются в таких вещах.
Все эти решения приведут к неправильному формату строк, если ваша кодировка / интерпратация файла неверна. У меня была действительно странная проблема при сериализации файла JSON в строку, пока я вручную не преобразовал его в UTF-8; Независимо от того, какое решение я пробовал, я получал только первого персонажа! Просто нужно остерегаться! :)





Использовать
#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 не так, как просили.
Выглядит красиво, но не компилируется. Изменения для компиляции сводят его к другим ответам на этой странице. ideone.com/EyhfWm
Почему цикл while?
Согласовано. Когда operator>> читает в std::basic_streambuf, он потребляет (то, что осталось) входной поток, поэтому цикл не нужен.
Никогда не записывайте в буфер const char * std :: string. Никогда! Это серьезная ошибка.
Зарезервируйте () пространство для всей строки в вашем std :: string, прочтите фрагменты из вашего файла разумного размера в буфер и добавьте их (). Насколько большими должны быть фрагменты, зависит от размера входного файла. Я почти уверен, что все другие переносимые и совместимые с STL механизмы будут делать то же самое (но могут выглядеть красивее).
Начиная с C++ 11, запись напрямую в буфер std::string гарантированно разрешена; и я считаю, что он работал правильно на всех фактических реализациях до этого
Начиная с C++ 17 у нас даже есть неконстантный метод std::string::data() для непосредственного изменения строкового буфера, не прибегая к уловкам вроде &str[0].
Согласившись с @ zett42, этот ответ фактически неверен
Самый короткий вариант: Live On Coliru
std::string str(std::istreambuf_iterator<char>{ifs}, {});
Требуется заголовок <iterator>.
Были сообщения, что этот метод медленнее, чем предварительное выделение строки и использование std::istream::read. Однако в современном компиляторе с включенной оптимизацией этого больше не происходит, хотя относительная производительность различных методов, по-видимому, сильно зависит от компилятора.
Не могли бы вы расширить этот ответ. Насколько это эффективно, читает ли он файл по символу за раз, в любом случае, чтобы предварительно выделить строковую память?
@ M.M Как я прочитал это сравнение, этот метод медленнее, чем чистый C++ метод чтения в предварительно выделенный буфер.
Вы правы, это тот случай, когда заголовок находится под образцом кода, а не над ним :)
Будет ли этот метод вызывать перераспределение памяти много раз?
@coincheung К сожалению, да. Если вы хотите избежать выделения памяти, вам необходимо вручную буферизовать чтение. Потоки ввода-вывода C++ - это довольно дерьмо.
@KonradRudolph Спасибо, я заметил, что есть еще один способ: stringstream ss; ifs >> ss.rdbuf(); str = ss.str();, будет ли этот метод также вызывать много перераспределений памяти, пожалуйста?
@coincheung Этот должен позволяет избежать повторного распределения, но на практике это глупо. «Канонический» способ чтения всего файла в C++ 17 - gist.github.com/klmr/849cbb0c6e872dff0fdcc54787a66103. К сожалению, очень многословно.
Что-то вроде этого не должно быть так уж плохо:
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. Более умная версия могла бы захватить весь буфер чтения и затем вызвать недополнение.
Вы должны проверить версию этого кода, которая использует std :: vector для начального чтения, а не строку. Намного намного быстрее.
Вы можете использовать функцию 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 раз медленнее.
Это будет работать только до тех пор, пока в вашем файле нет символов "eof" (например, 0x00, 0xff, ...). Если есть, вы прочитаете только часть файла.
Один из способов - сбросить буфер потока в отдельный поток памяти, а затем преобразовать его в 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;
}
Какой смысл делать это одним лайнером? Я всегда выбираю разборчивый код. Как самопровозглашенный энтузиаст VB.Net (IIRC), я думаю, вы должны понять это мнение?
@sehe: Я ожидал, что любой наполовину компетентный программист на C++ легко поймет эту однострочную формулировку. Это довольно скучно по сравнению с другими вещами, которые есть поблизости.
@DevSolar Что ж, более разборчивая версия на ~ 30% короче, без приведения и в остальном эквивалентна. Поэтому мой вопрос остается в силе: «Какой смысл делать его одинарным?»
@sehe Я никогда не говорил, что буду использовать oneliner в реальном коде. Это было больше, чтобы показать, что это можно сделать одним выражением.
@KonradRudolph Wokay. Рад это знать. Я просто немного сбит с толку, что вы тогда об этом упомянули. В любом случае, я проголосую за ваш другой ответ (??!), А затем за уловку :)
Я знаю, что это очень старый, но я только что провел профилирование нескольких методов и обнаружил, что получение размера файла и вызов in.read в буфер, предварительно выделенный для правильного размера, намного быстрее, чем это. Около 10x. Я использую VS2012 и тестирую файл размером 100 МБ.
@Dave Минимально быстрее - возможно. 10x? Это намекает на дефект в реализации стандартной библиотеки.
Просто хотел добавить, что тому, кто изучает C++, это с первого взгляда сложно понять.
@John Вот почему вы положили его на правильное функционирование. Большинство нетривиальных кодов трудно понять новичкам, если бы это было аргументом против использования такого кода, мы бы никогда не справились с работой.
Примечание: этот метод считывает файл в буфер строкового потока, а затем копирует весь этот буфер в string. Т.е. требует вдвое больше памяти, чем некоторые другие варианты. (Нет возможности переместить буфер). Для большого файла это было бы значительным штрафом, возможно, даже вызвав сбой выделения.
@ M.M Хороший момент, я не знаю, как это так долго оставалось незамеченным.
@sehe Как бы то ни было, я очень ценю лаконичность. Я не хочу вводить новую функцию только ради того, что в моей текущей программе является второстепенным функционалом, предполагающим чтение одной строки из файла для неважной цели. Просто требование добавить функцию для этого заставит меня даже не читать строку. В моем случае наличие одной строки кода позволяет одной строке кода не выделяться, поэтому я с радостью делаю это таким образом!
@DanNissenbaum Вы что-то путаете. Краткость действительно важна в программировании, но правильный способ добиться этого - разложить проблему на части и инкапсулировать их в независимые единицы (функции, классы и т. д.). Добавление функций не умаляет краткости; наоборот.
@KonradRudolph, я тебя слышу. С годами я отошел от добавления функций и классов для одноразового использования, потому что само их присутствие придает вес их важности. Приятно иметь возможность взглянуть на код и увидеть простой небольшой набор функций и классов, представляющих основные функции. Я начал использовать «правило трех» - если блок короткого кода используется только один или даже два раза, преимущество нет, имеющего функцию, может перевесить преимущество инкапсуляции. Лишь к тому времени, когда он дойдет до третьего использования, я иногда буду склонен инкапсулировать его. Этот "прихлебывающий файл" подходит.
@DanNissenbaum, поэтому были введены лямбды :)
Я считать, это решение работает, только если вы хотите читать файл в двоичном режиме. Если вы хотите читать его в текстовом режиме, istream_iterator - самый чистый способ. Это верно?
Этот способ медленный (потому что std :: stringstream медленный).
@Galik Медленно по сравнению с чем? Чтение в поток строк происходит очень быстро. Проблема в том, что строковые данные нельзя вывести из потока, их нужно скопировать.
Почему не dynamic_cast вместо static_cast? Разве мы не просто опускаемся?
@Ayxan Использование dynamic_cast действительно имеет смысл только в том случае, если вы не знаете, будет ли преобразование успешным, и проверить возвращаемое значение (или поймать потенциальный bad_cast). Однако мы знаем, что актерский состав здесь преуспел, поэтому нет необходимости хеджировать наши ставки. В идеале мы должны использовать приведение, при котором Только выполняет понижающее приведение и в то же время утверждает, что приведение будет успешным. Увы, в C++ такого приведения не существует.
Будет ли этот метод вызывать перераспределение памяти много раз?
это решение короткое, но запутанное. rdbuf() возвращает filebuf*. Как указатель на rdbuf заставляет stringstream читать содержимое файла? Я бы предпочел более подробный, но более понятный код, чем это волшебство.
@anton_rh Это не волшебство, но для этого нужно знать, как работают соответствующие участники, что задокументировано. Кажется, вам не хватает overload (9) на этой странице: en.cppreference.com/w/cpp/io/basic_ostream/operator_ltlt
См. этот ответ по аналогичному вопросу.
Для вашего удобства я перепубликую решение 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 файла;)
связанные: сравнение временных характеристик различных методов: Чтение всего файла сразу в C++
До сегодняшнего дня я ни разу не видел, чтобы tellg () сообщал о результатах, отличных от размера файла. На поиск источника ошибки у меня ушло несколько часов. Пожалуйста, не используйте tellg () для получения размера файла. stackoverflow.com/questions/22984956/…
не следует ли вам звонить в ifs.seekg(0, ios::end) раньше, чем в tellg? сразу после открытия файла указатель чтения находится в начале, поэтому tellg возвращает ноль
также вам нужно проверить наличие пустых файлов, так как вы разыменуете nullptr на &bytes[0]
ок, я пропустил ios::ate, поэтому думаю, что версия с явным продвижением в конец была бы более читабельной
Обратите внимание, что это решение работает только в двоичном режиме; тогда как OP запросил решение как для двоичного, так и для текстового режима.
Поскольку строки C++ 11 гарантированно имеют непрерывное хранилище, вы можете напрямую использовать строку вместо вектора и, таким образом, пропустить вектор для копирования строки.
Это решение непереносимо, так как .tellg() не гарантирует возврата размера файла. (а на практике некоторые системы этого не делают).
Если у вас 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], если он не поддерживает неконстантные данные std :: basic_string.
Это может вызвать неопределенное поведение; открытие файла в текстовом режиме дает другой поток, чем файл на диске в некоторых операционных системах.
Первоначально разработан как boost::filesystem, поэтому вы также можете использовать boost, если у вас нет C++ 17
Открытие файла с помощью одного API и получение его размера с помощью другого, похоже, требует несогласованности и условий гонки.
У меня недостаточно репутации, чтобы напрямую комментировать ответы, используя 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);
}
Более того, tellg() возвращает не размер, а токен. Многие системы используют байтовое смещение в качестве токена, но это не гарантируется, а некоторые системы этого не делают. Посмотрите, например, этот ответ.
Это решение добавляет проверку ошибок к методу, основанному на 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 (<<) используйте оператор извлечения ifstream (>>), который устанавливает eofbit. Затем проверьте состояние неисправности file_stream.fail() && !file_stream.eof().
Важно отметить, что когда file_stream >> str_stream.rdbuf() обнаруживает законный сбой, он никогда не должен устанавливать eofbit (согласно моему пониманию спецификации). Это означает, что вышеуказанной проверки достаточно для обнаружения законных сбоев.
#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);
Вот версия, использующая новую библиотеку файловой системы с достаточно надежной проверкой ошибок:
#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()); };
infile.open также может принимать std::string без преобразования с .c_str()
filepath - это не std::string, это std::filesystem::path. Оказывается, std::ifstream::open может принять и один из них.
@DavidG, std::filesystem::path неявно конвертируется в std::string
Согласно cppreference.com, функция-член ::open на std::ifstream, которая принимает std::filesystem::path, работает так, как если бы метод ::c_str() был вызван на пути. Базовый ::value_type путей - это char в POSIX.
Обновленная функция, основанная на решении CTT:
#include <string>
#include <fstream>
#include <limits>
#include <string_view>
std::string readfile(const std::string_view path, bool binaryMode = true)
{
std::ios::openmode openmode = std::ios::in;
if (binaryMode)
{
openmode |= std::ios::binary;
}
std::ifstream ifs(path.data(), openmode);
ifs.ignore(std::numeric_limits<std::streamsize>::max());
std::string data(ifs.gcount(), 0);
ifs.seekg(0);
ifs.read(data.data(), data.size());
return data;
}
Есть два важных отличия:
tellg() не гарантирует возврата смещения в байтах с начала файла. Вместо этого, как указал Пузомор Хорватия, это скорее токен, который можно использовать в вызовах fstream. gcount(), однако, делает возвращает количество неформатированных байтов, извлеченных последними. Поэтому мы открываем файл, извлекаем и отбрасываем все его содержимое с помощью ignore(), чтобы получить размер файла, и строим на его основе строку вывода.
Во-вторых, нам не нужно копировать данные файла с std::vector<char> на std::string, напрямую записывая данные в строку.
С точки зрения производительности это должно быть самым быстрым, если заранее выделить строку подходящего размера и вызвать read() один раз. Интересный факт: использование ignore() и countg() вместо ate и tellg() в gcc компилируется до почти то же самое, бит за битом.
Этот код не работает, я получаю пустую строку. Думаю, вы хотели ifs.seekg(0) вместо ifs.clear() (тогда работает).
#include <iostream>
#include <fstream>
#include <string.h>
using namespace std;
main(){
fstream file;
//Open a file
file.open("test.txt");
string copy,temp;
//While loop to store whole document in copy string
//Temp reads a complete line
//Loop stops until temp reads the last line of document
while(getline(file,temp)){
//add new line text in copy
copy+=temp;
//adds a new line
copy+ = "\n";
}
//Display whole document
cout<<copy;
//close the document
file.close();
}
Добавьте описание.
посетите и проверьте как ответить на вопрос.
Поскольку это похоже на широко используемую утилиту, мой подход заключался бы в поиске и предпочтении уже доступных библиотек решениям, сделанным вручную, особенно если библиотеки 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', который перезаписывается содержимым файла.
Хотя это не (совсем) точная копия, это тесно связано с: как предварительно выделить память для объекта std :: string? (который, в отличие от утверждения Конрада выше, включал код для этого, считывая файл непосредственно в место назначения, без создания дополнительной копии).