Как мне прочитать файл в std::string
, т.е. прочитать весь файл одновременно?
Текстовый или двоичный режим должен быть указан вызывающим абонентом. Решение должно быть совместимым со стандартами, переносимым и эффективным. Он не должен без необходимости копировать данные строки и должен избегать перераспределения памяти при чтении строки.
Один из способов сделать это - указать размер файла, изменить размер std::string
и ввести fread()
в std::string
«s const_cast<char*>()
» data()
. Это требует, чтобы std::string
данные были смежными, что не требуется стандартом, но, похоже, имеет место для всех известных реализаций. Что еще хуже, если файл читается в текстовом режиме, std::string
размер файла может не совпадать с размером файла.
Полностью правильные, совместимые со стандартами и портативные решения могут быть построены с использованием std::ifstream
символов rdbuf()
a в a std::ostringstream
и оттуда в a std::string
. Однако это может скопировать строковые данные и / или излишне перераспределить память.
- Все ли соответствующие реализации стандартной библиотеки достаточно умны, чтобы избежать ненужных накладных расходов?
- Есть ли другой способ сделать это?
- Я пропустил какую-то скрытую функцию Boost, которая уже обеспечивает желаемую функциональность?
void slurp(std::string& data, bool is_binary)
rdbuf
(тот, что в принятом ответе) не самый быстрыйread
.Ответы:
Один из способов - сбросить буфер потока в отдельный поток памяти, а затем преобразовать его в
std::string
:Это красиво лаконично. Однако, как отмечено в вопросе, это создает избыточную копию, и, к сожалению, принципиально нет способа удалить эту копию.
К сожалению, единственное реальное решение, которое позволяет избежать избыточных копий, - это делать чтение вручную в цикле. Поскольку C ++ теперь имеет гарантированные смежные строки, можно написать следующее (≥C ++ 14):
источник
string
. Т.е. требуется вдвое больше памяти, чем некоторым другим параметрам. (Нет способа переместить буфер). Для большого файла это было бы значительным штрафом, возможно даже вызвавшим сбой выделения.Смотрите этот ответ на похожий вопрос.
Для вашего удобства я публикую решение CTT:
Это решение привело к сокращению времени выполнения примерно на 20% по сравнению с другими представленными здесь ответами, когда в среднем принималось 100 прогонов против текста Моби Дика (1,3 млн.). Неплохо для портативного решения C ++, я хотел бы увидеть результаты mmap'ing файла;)
источник
ifs.seekg(0, ios::end)
раньшеtellg
? сразу после открытия файла указатель чтения находится в начале и поэтомуtellg
возвращает нольnullptr
по&bytes[0]
ios::ate
, так что я думаю, что версия с явным перемещением в конец была бы более читабельнойСамый короткий вариант: Live On Coliru
Требуется заголовок
<iterator>
.Было несколько сообщений, что этот метод медленнее, чем предварительное выделение строки и ее использование
std::istream::read
. Однако на современном компиляторе с включенной оптимизацией это, похоже, уже не так, хотя относительная производительность различных методов, по-видимому, сильно зависит от компилятора.источник
использование
или что-то очень близкое. У меня нет открытой ссылки stdlib, чтобы перепроверить себя.
Да, я понимаю, я не написал
slurp
функцию, как просили.источник
operator>>
чтении в astd::basic_streambuf
он будет потреблять (что осталось от) входной поток, поэтому цикл не нужен.Если у вас есть C ++ 17 (std :: filesystem), также есть этот способ (который определяет размер файла
std::filesystem::file_size
вместоseekg
иtellg
):Примечание : вам может понадобиться использовать,
<experimental/filesystem>
иstd::experimental::filesystem
если ваша стандартная библиотека еще не полностью поддерживает C ++ 17. Вам также может потребоваться заменитьresult.data()
на,&result[0]
если он не поддерживает неконстантные данные std :: basic_string .источник
boost::filesystem
так, вы также можете использовать boost, если у вас нет c ++ 17У меня недостаточно репутации, чтобы напрямую комментировать ответы
tellg()
.Обратите внимание, что
tellg()
при ошибке может вернуть -1. Если вы передаете результатtellg()
в качестве параметра выделения, вы должны сначала проверить его.Пример проблемы:
В приведенном выше примере, если возникнет
tellg()
ошибка, он вернет -1. Неявное приведение между sign (то есть результатомtellg()
) и unsigned (т.е. аргументомvector<char>
конструктора) приведет к тому, что ваш вектор ошибочно выделит очень большое количество байтов. (Вероятно, 4294967295 байт или 4 ГБ.)Модифицируя ответ paxos1977, чтобы учесть вышеизложенное:
источник
Это решение добавляет проверку ошибок в метод, основанный на rdbuf ().
Я добавляю этот ответ, потому что добавление проверки ошибок в оригинальный метод не так тривиально, как вы ожидаете. Оригинальный метод использует оператор вставки stringstream (
str_stream << file_stream.rdbuf()
). Проблема в том, что это устанавливает битовый поток stringstream, когда никакие символы не вставлены. Это может быть связано с ошибкой или с пустым файлом. Если вы проверите на наличие ошибок, проверив бит-бит, вы получите ложное срабатывание при чтении пустого файла. Как вы устраняете неоднозначность законного сбоя при вставке любых символов и «сбоя» при вставке любых символов, потому что файл пуст?Вы можете явно проверить наличие пустого файла, но это больше кода и связанной с этим проверки ошибок.
Проверка состояния сбоя
str_stream.fail() && !str_stream.eof()
не работает, потому что операция вставки не устанавливает eofbit (ни для потока ostring, ни для потока if).Таким образом, решение состоит в том, чтобы изменить операцию. Вместо использования оператора вставки ostringstream (<<), используйте оператор извлечения ifstream (>>), который устанавливает eofbit. Затем проверьте состояние неисправности
file_stream.fail() && !file_stream.eof()
.Важно отметить, что когда
file_stream >> str_stream.rdbuf()
встречается законный сбой, он никогда не должен устанавливать eofbit (согласно моему пониманию спецификации). Это означает, что вышеупомянутой проверки достаточно для обнаружения законных сбоев.источник
Примерно так должно быть не так уж плохо:
Преимущество здесь в том, что мы сначала делаем резерв, поэтому нам не нужно увеличивать строку при чтении. Недостатком является то, что мы делаем это символ за символом. Более умная версия может получить весь прочитанный buf и затем вызвать underflow.
источник
Вот версия, использующая новую библиотеку файловой системы с достаточно надежной проверкой ошибок:
источник
infile.open
можно также принятьstd::string
без конвертации с.c_str()
filepath
неstd::string
, этоstd::filesystem::path
. Оказывается,std::ifstream::open
может принять один из них.std::filesystem::path
неявно конвертируется вstd::string
::open
функция-член,std::ifstream
которая принимает,std::filesystem::path
работает так, как если бы::c_str()
метод был вызван на пути. Основа::value_type
путей находитсяchar
под POSIX.Вы можете использовать функцию 'std :: getline' и указать 'eof' в качестве разделителя. Полученный код немного неясен, хотя:
источник
Никогда не записывайте в буфер stst :: string const char *. Никогда никогда! Это большая ошибка.
Зарезервируйте () пространство для всей строки в вашем std :: string, прочитайте куски из вашего файла разумного размера в буфер и добавьте () его. Размер фрагментов зависит от размера входного файла. Я уверен, что все другие переносимые и STL-совместимые механизмы будут делать то же самое (но могут выглядеть красивее).
источник
std::string
буфер; и я считаю, что до этого все работало корректно на всех реальных реализацияхstd::string::data()
метод для непосредственного изменения строкового буфера без использования подобных трюков&str[0]
.использование:
источник
Обновленная функция, основанная на решении CTT:
Есть два важных различия:
tellg()
не гарантируется возврат смещения в байтах с начала файла. Вместо этого, как указала Puzomor Croatia, это скорее токен, который можно использовать в вызовах fstream.gcount()
однако же возвращают количество байт неотформатированных последний извлеченные. Поэтому мы открываем файл, извлекаем и удаляем все его содержимое с помощьюignore()
чтобы получить размер файла, и строим выходную строку на основе этого.Во-вторых, мы избегаем необходимости копировать данные файла из a
std::vector<char>
в astd::string
путем прямой записи в строку.С точки зрения производительности, это должно быть абсолютно быстрым, выделяя строку соответствующего размера заранее и вызывая
read()
один раз. Как интересный факт, использованиеignore()
иcountg()
вместоate
иtellg()
на gcc компилирует почти одно и то же , по крупицам.источник
ifs.seekg(0)
вместоifs.clear()
(тогда это работает).источник