[Перевод] Работа с файлами в языке программирования D
На этот пост меня вдохновила статья, написанная несколько недель назад и озаглавленная Работа с файлами в Go (статья на английском языке — прим. перев.). В этой статье автор описывает множество способов взаимодействия с файлами, подробно останавливаясь на особенностях языка Go. И я подумал написать сопутствующий пост, на этот раз описав, как работать с файлами в языке D.
Взаимодействие с файлами — одна из фундаментальных задач любого языка программирования, и хотя решение таких задач — явление обычное, может быть не всегда очевидно, как именно решаются некоторые проблемы на языке D. Надеюсь, эта статья покажет простоту и мощь языка D при работе с файлами.
В некоторых из приведённых ниже примеров кода используется так называемый единый синтаксис вызова функций (Uniform Function Call Syntax, UFCS). Пусть он вас не смущает: простое его объяснение можно найти здесь (пока тоже англ. — прим. перев.).
Чтение и запись
Открываем и закрываем файлыНижеследующий код показывает, как открывать и закрывать файлы безопасным способом. Вообще говоря, D предоставляет тонкие обёртки вокруг функций стандартной библиотеки языка C, однако работа с дескрипторами файлов небезопасна и приводит к ошибкам по многим причинам. Тип File предоставляет безопасную работу, автоматическое закрытие файлов и другие удобные возможности. Лежащий в основе дескриптор хранится с подсчётом ссылок, таким образом, когда последняя переменная типа File выходит за область использования, дескриптор автоматически закрывается.
Если брошено исключение, то возникла ошибка с доступом к файлу, и чтобы выяснить, что пошло не так, можно обратиться к свойству errno этого исключения.
Поскольку тип File — это обёртка вокруг функции языка C, возвращённый код ошибки будет равен одной из констант, определённых в core.stdc.errno . Это основной способ доступа к файлам и обработки возникающих ошибок. Расширенную информацию можно собрать с помощью функции std.file.getAttributes , которая возвращает беззнаковое целое число. Это число содержит несколько битовых флагов которые устанавливаются по-разному в зависимости от операционной системы. Более подробную информацию об этих флагах можно найти здесь (англ. — прим. перев.).
Ищем позицию в файлеИногда перед тем, как начать чтение или запись, бывает нужно перейти на конкретную позицию в файле. Следующий пример показывает, как изменить позицию с различных начальных точек.
Записываем байты в файлЭтот пример показывает, как записать байты в файл.
Быстрая запись в файлИногда бывает здорово просто сделать дамп буфера данных в файл и оставить на попечение библиотеки открытие и закрытие файла. Следующий пример показывает, как это делается.
Это запись массива байтов в файл, но здесь так же легко могла быть и строка.
Записываем строки в файлПри работе с файлами всегда бывает много записи и чтения строк из файла. Ниже показаны различные способы записи строки в файл.
Эти способы могут быть более удобны в зависимости от различных сценариев работы.
Использование буфера ввода-вывода перед записью в файлВ качестве техники оптимизации иногда бывает необходимо перед записью в файл записать данные в буфер в оперативной памяти, чтобы сэкономить на вводе-выводе на диск. Следующий пример показывает один из многих способов создания и записи такого буфера.
Использование буфера таким способом позволяет очень быстро записывать данные в память до записи на диск. Это экономит время на отсутствии маленьких этапов записи на диск и снижает износ самого диска.
Читаем байты из файлаСледующий пример показывает, как читать байты из файла.
При чтении байтов таким способом вы должны предоставить буфер, который будет принимать считываемые данные. В этом примере для такого буфера используется динамический массив и выделяется 1024 байта перед чтением. Метод rawRead заполняет буфер и возвращает срез этого буфера. Длина буфера — это максимальное число байтов, которые будут прочитаны.
Быстрое чтение из файлаИногда бывает здорово просто прочитать данные из файла и оставить на попечение библиотеки открытие и закрытие файла. Ниже показано, как это сделать.
В результате возвращается массив типа void. Его можно привести к какому-нибудь более полезному типу. В этом примере он был приведён к типу «массив байтов».
Читаем n байт из файлаВ этом примере снова используется функция read , но на сей раз со вторым параметром, задающим максимальное число байтов для чтения. Если файл меньше заданного лимита, будут возвращены только данные из файла.
Как и в предыдущих случаях, результат можно преобразовать и в другие типы массивов.
Чтение файла порциямиВ следующем примере файл читается порциями по 1024 байта.
Метод byChunk возвращает входной диапазон байтов, который считывает из дескриптора файла за один раз только определённую порцию. В данном случае каждый вызов вернёт максимум 1024 байта. При каждом вызове буфер используется снова, поэтому если нужно сохранить данные между вызовами, необходимо их копировать.
Читаем строки из файлаЭти примеры показывают, как читать строки из файла.
Хотя вышеприведённый пример удобен для чтения строк из файла, у него есть один недостаток: функция readln выделяет новый буфер для чтения каждой строки.
Из-за этого ввиду возможной проблемы производительности существует перегруженный метод, принимающий буфер в качестве параметра:
Затем этот буфер можно использовать снова для каждой считываемой строки (что увеличивает производительность). Недостаток этого способа состоит в том, что если вам нужно сохранять данные между вызовами, их придётся копировать. D позволяет вам решать, что лучше.
Читаем файл как диапазон строкЧтение файла как диапазона позволяет вам использовать множество типовых алгоритмов, определённых в библиотеке Phobos. В примере ниже показано, как это делается.
Метод byLine возвращает входной диапазон, который считывает из дескриптора файла одну строку за раз. При каждом вызове буфер используется снова, поэтому если вам нужно сохранять данные между вызовами, вы должны их копировать. Впрочем, существует удобный метод byLineCopy , который делает это автоматически.
Быстрое чтение целого файла как одна строкаНиже показано, как прочитать файл целиком в одну переменную типа «строка».
Этот код читает и проверяет текстовый файл. Конвертация кодировок (ширины символов) не производится. Если ширина символов в файле не соответствует указанному строковому типу, валидация завершится ошибкой.
Базовые операции
Создание пустого файлаНижеприведённый код создаёт пустой файл (если он уже не существует) при инициализации структуры типа File . Если файл с таким именем уже существует, его содержимое удаляется, и файл считается пустым.
Проверка на существование файлаЭтот код просто проверяет, существует ли файл.
Переименование и перемещение файлаЭтот код переименовывает и/или перемещает файл. Если целевой файл существует, он будет перезаписан.
Копирование файлаЭтот код копирует файл. Если целевой файл существует, он будет перезаписан.
Удаление файлаЭтот код просто удаляет файл.
Получение информации о файлеЭтот код получает информацию о файле подобно тому, как вы бы сделали это командой stat (англ.: сорри, в Википедии пока нет русскоязычной статьи — прим. перев.) в POSIX-совместимой операционной системе. Ниже показано получение только кроссплатформенной информации. Другая информация доступна в зависимости от операционной системы, её можно получить декодированием свойства attributes .
Усечение существующего файлаЭтот код усекает существующий файл до 100 байт. Если исходный файл меньше, усечения не происходит.
Архивирование
Создание zip-архиваОсновываясь на следующих примерах, этот код показывает, как создать zip-архив.
Чтение zip-архиваВ следующем примере показано, как прочитать zip-архив.
Сжатие данных
Запись сжатых данных в файлВ следующем примере показано, как сжимать данные перед отправкой их в файл.
В предыдущем примере сжимается строка, однако сжать можно любые данные. Модуль std.zlib использует библиотеку Zlib языка C.
Чтение сжатых данных из файлаНиже показано, как читать сжатые данные из файла.
POSIX-операции
Изменение прав доступа к файлуЭтот код изменяет права доступа к файлам в POSIX-совместимых операционных системах, таких, как Linux или Mac OS. В библиотеке Phobos для этой задачи нет кроссплатформенного решения, поэтому мы можем использовать только системные вызовы, специфичные для POSIX.
Системный вызов chmod работает абсолютно идентично команде chmod из командной оболочки Unix. Указывается имя файла и его новые права доступа (выраженные в виде восьмеричного числа). Чтобы изменять файл таким образом, вам нужны права доступа на саму эту операцию. Для этого вам нужно быть владельцем файла или суперпользователем.
Изменение владельца файлаЭтот код изменяет владельца файла в POSIX-совместимых системах. Когда вы становитесь владельцем, можно изменять права доступа файла, не будучи суперпользователем.
Системный вызов chown работает абсолютно аналогично команде chown оболочки Unix. Указывается имя файла и его новый владелец и группа. Чтобы изменять владельца файла, ваша программа должна обладать правами суперпользователя.
Создание жёстких и символических ссылокИногда в POSIX-совместимых системах бывает нужно создать жёсткую или символическую ссылку. В следующем примере показано, как создать жёсткую ссылку.