- PVSM.RU - https://www.pvsm.ru -
UPD: добавил в конец поста бонус для читателей Хабра.
Привет! Я время от времени рассказываю на Хабре о решениях распространённых задач на C++ и вообще люблю делиться опытом. Поэтому даже написал целую книгу, которая называется «Разработка приложений на С++ с использованием Boost». Она может быть интересна разработчикам, которые уже немного знакомы со стандартной библиотекой языка, хотят глубже изучить Boost, упростить и повысить качество разработки приложений. Уверен, что информация, которую я собрал в книге, будет полезна — всё больше библиотек Boost становятся частью стандарта. Сегодня предлагаю прочитать главу, посвящённую работе с файлами. В ней я рассказываю о перечислении файлов в каталоге, стирании и создании файлов и каталогов, а также о самом быстром способе чтения. Надеюсь, будет интересно. И, пожалуйста, не забывайте делиться впечатлениями в комментариях.
Существуют функции и классы стандартной библиотеки для чтения и записи данных в файлы. Но до появления C++17 в ней не было функций для вывода списка файлов в каталоге, получения типа файла или получения прав доступа к файлу.
Давайте посмотрим, как можно исправить эту несправедливость с помощью Boost. Мы будем создавать программу, которая перечисляет имена файлов, права на запись и типы файлов в текущем каталоге.
Знание основ C++ более чем достаточно для использования этого рецепта. Этот рецепт требует линковки с библиотеками boost_system и boost_filesystem.
Этот и последующий рецепты посвящены переносимым оберткам для работы с файловой системой.
#include <boost/filesystem/operations.hpp>
#include <iostream>
int main() {
boost::filesystem::directory_iterator begin("./");
boost::filesystem::directory_iterator end;
for (; begin != end; ++ begin) {
boost::filesystem::file_status fs =
boost::filesystem::status(*begin);{
switch (fs.type()) {
case boost::filesystem::regular_file:
std::cout << "FILE ";
break;
case boost::filesystem::symlink_file
std::cout << "SYMLINK ";
break;
case boost::filesystem::directory_file:
std::cout << "DIRECTORY ";
break;
default:
std::cout << "OTHER ";
break;
}
if (fs.permissions() & boost::filesystem::owner_write) {
std::cout << "W ";
} else {
std::cout << " ";
}
std::cout << *begin << 'n';
} /*for*/
} /*main*/
Готово. Теперь, если мы запустим программу, она выведет что-то вроде этого:
FILE W "./main.o"
FILE W "./listing_files"
DIRECTORY W "./some_directory"
FILE W "./Makefile"
Функции и классы Boost.Filesystem просто оборачивают системные вызовы для работы с файлами.
Обратите внимание на использование знака "./" на этапе 2. Системы POSIX используют косую черту для указания путей; Windows по умолчанию использует обратную косую черту. Тем не менее Windows также понимает косую черту, а даже если бы не понимала, то библиотека Boost позаботилась бы о неявном преобразовании формата пути.
Посмотрите на этап 3, где мы вызываем конструктор по умолчанию для класса boost::filesystem::directory_iterator. Этот конструктор работает по аналогии с конструктором по умолчанию класса std::istream_iterator, – создает итератор конца диапазона.
Этап 4 сложен не потому, что эту функцию трудно понять, а из-за того, что происходит много преобразований. Разыменование итератора begin возвращает boost::filesystem::directory_entry, который неявно преобразуется в boost::filesystem::path, использующийся в качестве параметра для функции boost::filesystem::status. На самом деле можно написать намного лучше:
boost::filesystem::file_status fs = begin->status();
Совет:
Внимательно прочитайте справочную документацию, чтобы избежать ненужных неявных преобразований.
Этап 5 очевиден, поэтому мы переходим к этапу 6, где неявное преобразование в boost::filesystem::path происходит снова. Более явное решение выглядит так:
std::cout << begin->path() << 'n';
Здесь begin->path() возвращает константную ссылку на переменную
boost::filesystem::path, которая содержится в boost::filesystem::directory_entry.
Boost.Filesystem является частью C++17. Все содержимое в C++17 находится в одном заголовочном файле в пространстве имен std::filesystem::. Версия стандартной библиотеки несколько отличается от Boost-версии, в основном за счет использования перечислений с областью видимости (enum class) там, где Boost.Filesystem использовала просто перечисление без области видимости.
Совет:
Есть класс directory_entry, который обеспечивает кеширование информации о файловой системе. Так что если вы много работаете с файловой системой и запрашиваете различную информацию, попробуйте использовать directory_entry для лучшей производительности.
Как и в случае с другими библиотеками Boost, Boost.Filesystem работает с компиляторами для стандарта, предшествующего C++17, и даже с компиляторами для стандарта, предшествующего C++11.
Давайте рассмотрим следующие строки кода:
std::ofstream ofs("dir/subdir/file.txt");
ofs << "Boost.Filesystem is fun!";
В этих строках мы пытаемся записать что-то в файл file.txt в каталоге dir/ subdir. Если такой директории нет, эта попытка будет неудачной.
В этом рецепте мы создадим каталог и подкаталог, запишем некие данные в файл и попробуем создать символическую ссылку. Если создание символической ссылки не удается, то мы удалим созданные сущности. В примере мы также будем избегать использования исключений в качестве механизма сообщения об ошибках, отдавая предпочтение чему-то вроде кодов возврата.
Давайте посмотрим, как можно сделать это элегантно, используя Boost.
Для этого рецепта требуются базовые знания C++ и класса std::ofstream.
Boost.Filesystem не является библиотекой header-only, поэтому код в этом ре-
цепте требуется линковать с библиотеками boost_system и boost_filesystem.
Мы продолжаем работать с переносимыми обертками для файловой системы и в этом рецепте посмотрим, как изменить содержимое каталога.
#include <boost/filesystem/operations.hpp>
#include <cassert>
#include <fstream>
int main() {
boost::system::error_code error;
boost::filesystem::create_directories("dir/subdir", error);
assert(!error);
std::ofstream ofs("dir/subdir/file.txt");
ofs << "Boost.Filesystem is fun!";
assert(ofs);
ofs.close();
boost::filesystem::create_symlink(
"dir/subdir/file.txt", "symlink", error);
if (!error) {
std::cerr << "Symlink createdn";
assert(boost::filesystem::exists("symlink"));
} else {
std::cerr << "Failed to create a symlinkn";
boost::filesystem::remove_all("dir", error);
assert(!error);
boost::filesystem::remove("symlink", error);
assert(!error);
} /*if (!error)*/
} /*main*/
boost::system::error_code может хранить информацию об ошибках и широко используется во всех библиотеках Boost.
Если вы не предоставите экземпляр boost::system::error_code для функций Boost.Filesystem, код будет компилироваться. В этом случае при возникновении ошибки будет выброшено исключение boost::filesystem::filesystem_error.
Внимательно посмотрите на этап 3. Мы использовали функцию boost::filesystem::create_directories вместо boost::filesystem::create_directory, потому что последняя не может создавать вложенные подкаталоги. Та же самая история с boost::filesystem::remove_all и boost::filesystem::remove. Первая удаляет каталоги, которые могут содержать файлы и подкаталоги. Вторая удаляет один файл.
Остальные шаги просты для понимания и не должны вызывать проблем.
Класс boost::system::error_code является частью C++11. Его можно найти в заголовочном файле <system_error> в пространстве имен std::. Классы Boost.Filesystem являются частью C++17.
Наконец, небольшая рекомендация для тех, кто собирается использовать Boost.Filesystem. Когда ошибки при работе с файловой системой являются частым явлением, или приложение требует высокой отзывчивости/производительности, используйте класс boost::system::error_codes. В противном случае для обработки ошибок перехват исключений будет предпочтительнее и надежнее.
Рецепт «Перечисление файлов в каталоге» также содержит информацию о Boost. Filesystem. Прочтите официальную документацию по адресу http://boost.org/libs/filesystem [1], где приводится больше информации и примеров.
В интернете люди спрашивают: «Какой самый быстрый способ чтения файлов?» Давайте усложним задачу для этого рецепта: какой самый быстрый и переносимый способ чтения двоичных файлов?
Для этого рецепта требуются базовые знания C++ и std::fstream.
Техника из этого рецепта широко используется приложениями, чувствительными к производительности ввода-вывода. Это самый быстрый способ чтения файлов.
Техника из этого рецепта широко используется приложениями, чувствительными к производительности ввода-вывода. Это самый быстрый способ чтения файлов.
#include <boost/interprocess/file_mapping.hpp>
#include <boost/interprocess/mapped_region.hpp>
const boost::interprocess::mode_t mode = boost::interprocess::read_only;
boost::interprocess::file_mapping fm(filename, mode);
boost::interprocess::mapped_region region(fm, mode, 0, 0);
const char* begin = static_cast<const char*>(
region.get_address()
);
Готово! Теперь мы можем работать с файлом, как с обычной памятью:
const char* pos = std::find(
begin, begin + region.get_size(), '1'
);
Все популярные операционные системы имеют возможность отображать файл в адресное пространство процессов. После того как такое отображение было выполнено, процесс может работать с этими адресами так же, как с обычной памятью. Операционная система сама заботится обо всех файловых операциях, таких как кеширование и упреждающее чтение.
Почему это быстрее, чем традиционные операции чтения и записи? Это связано с тем, что в большинстве случаев чтение и запись реализуются как отображение в память и копирование данных в указанный пользователем буфер. Таким образом, чтение обычно делает немного больше, чем отображение.
Как и в случае с std::fstream из стандартной библиотеки, мы должны передать режим открытия файла. См. этап 2, где мы предоставили режим boost::interprocess::read_only.
См. этап 3, где мы отобразили весь файл сразу. Эта операция действительно очень быстрая, потому что ОС не читает все данные с диска, а сразу возвращает нам управление и ожидает запросов к части отображаемой области. После запроса ОС загружает запрошенную часть файла с диска в память. Как мы видим, операции отображения в память являются ленивыми, а размер отображаемой области не влияет на производительность.
Однако 32-разрядная ОС не может отображать в память большие файлы, поэтому вам придется отображать их по частям. Операционные системы POSIX (Linux) требуют определения макроса _FILE_OFFSET_ BITS=64 для всего проекта, чтобы работать с большими файлами на 32-битной платформе. В противном случае ОС не сможет отобразить части файла, размер находится за границей первых 4 ГБ.
Теперь пришло время измерить производительность:
$ TIME="%E" time ./reading_files m
mapped_region: 0:00.08
$ TIME="%E" time ./reading_files r
ifstream: 0:00.09
$ TIME="%E" time ./reading_files a
C: 0:00.09
Как и ожидалось, отображенные в память файлы немного быстрее по сравнению с традиционными операциями чтения. Также видно, что чистые методы C имеют такую же производительность, что и класс C++ std::ifstream, поэтому по возможности не используйте FILE* функции в C++. Они предназначены только для C, а не для C++!
Для обеспечения оптимальной производительности std::ifstream не забудьте открыть файлы в двоичном режиме и читать данные блоками:
std::ifstream f(filename, std::ifstream::binary);
// ...
char c[kilobyte];
f.read(c, kilobyte);
К сожалению, классы для отображения файлов в память не являются частью C++20, и, похоже, их не будет и в C++23.
Запись в области с отображением в память также является очень быстрой операцией. Операционная система кеширует записи и не сбрасывает изменения на диск немедленно. Существует разница между кешированием данных в ОС и std::ofstream. В случае std::ofstream данные кешируются приложением, и если работа приложения аварийно завершается, закешированные данные могут быть потеряны. Когда данные кешируются ОС, завершение работы приложения не приводит к их потере. Сбои питания и сбои ОС приводят к потере данных в обоих случаях.
Если несколько процессов отображают один файл и один из процессов изменяет отображаемую область, то изменения сразу видны другим процессам (даже без фактической записи данных на диск! Современные ОС очень умные! Только не забывайте про синхронизацию доступа).
Библиотека Boost.Interprocess содержит множество полезных функций для работы с системой; не все из них описаны в этой книге. Вы можете прочитать больше об этой грандиозной библиотеке на официальном сайте: http://boost.org/libs/interprocess [3].
Автор: Antony Polukhin
Источник [5]
Сайт-источник PVSM.RU: https://www.pvsm.ru
Путь до страницы источника: https://www.pvsm.ru/programmirovanie/358754
Ссылки в тексте:
[1] http://boost.org/libs/filesystem: http://boost.org/libs/filesystem
[2] http://www.open-std.org/jtc1/sc22/wg21/docs/papers/2017/n4659.pdf: http://www.open-std.org/jtc1/sc22/wg21/docs/papers/2017/n4659.pdf
[3] http://boost.org/libs/interprocess: http://boost.org/libs/interprocess
[4] сайте издательства: https://dmkpress.com/catalog/computer/programming/c/978-5-97060-868-5/
[5] Источник: https://habr.com/ru/post/526538/?utm_source=habrahabr&utm_medium=rss&utm_campaign=526538
Нажмите здесь для печати.