Работа с файлами и каталогами в NIO.2
В предыдущих статьях я обсуждал создание ( создание файлов и каталогов ) и выбор (просмотр и фильтрация содержимого каталогов ) файлов и каталогов. Последний логичный шаг — выяснить, что мы можем с ними сделать и как. Это часть библиотеки, которая была значительно переработана. Обновления в этой области включают гарантию атомарности определенных операций, улучшения API, оптимизацию производительности, а также введение надлежащей иерархии исключений, которая заменила методы boolean возврата из предыдущих версий библиотеки ввода-вывода.
Открытие файла
Прежде чем приступить к чтению и записи в файл, нам необходимо рассмотреть один общий принцип этих операций — способ открытия файлов. Способ открытия файлов напрямую влияет на результаты этих операций, а также на их производительность. Давайте взглянем на стандартные параметры открытия файлов, содержащиеся в enum java.nio.file.StandardOpenOption :
Значение | Описание |
---|---|
APPEND | Если файл открыт для доступа WRITE, то байты будут записаны в конец файла, а не в его начало. |
CREATE | Создайте новый файл, если он не существует. |
CREATE_NEW | Создайте новый файл, если файл уже существует. |
DELETE_ON_CLOSE | Удалить по закрытию. |
DSYNC | Требует, чтобы каждое обновление содержимого файла было записано синхронно на базовое устройство хранения. |
READ | Открыть для чтения. |
SPARSE | Разреженный файл. |
SYNC | Требует, чтобы каждое обновление содержимого файла или метаданных было записано синхронно на базовое устройство хранения. |
TRUNCATE_EXISTING | Если файл уже существует и он открыт для доступа WRITE, его длина сокращается до 0. |
WRITE | Открыть для записи. |
Это все стандартные параметры, которые могут понадобиться вам как разработчику для правильной обработки открытия файлов, будь то для чтения или записи.
Чтение файла
Когда дело доходит до чтения файлов, NIO.2 предлагает несколько способов сделать это — каждый со своими плюсами и минусами. Эти подходы заключаются в следующем:
- Чтение файла в байтовый массив
- Использование небуферизованных потоков
- Использование буферизованных потоков
Давайте посмотрим на первый вариант. Class Files предоставляет метод readAllBytes для выполнения именно этого. Чтение файла в байтовый массив кажется довольно простым действием, но это может подойти только для очень ограниченного диапазона файлов. Поскольку мы помещаем весь файл в память, мы должны учитывать его размер. Использование этого метода целесообразно только тогда, когда мы пытаемся читать небольшие файлы, и это можно сделать мгновенно. Это довольно простая операция, представленная в следующем фрагменте кода:
Приведенный выше код сначала считывает файл в байтовый массив, а затем создает строковый объект, содержащий содержимое указанного файла, со следующим выводом:
Lorem ipsum dolor sit amet, consectetur adipiscing elit. Aliquam sit amet justo nec leo euismod porttitor. Vestibulum id sagittis nulla, eu posuere sem. Cras commodo, massa sed semper elementum, ligula orci malesuada tortor, sed iaculis ligula ligula et ipsum.
Когда нам нужно прочитать содержимое файла в виде строки, мы можем использовать приведенный выше код. Однако это решение не так уж чисто, и мы можем использовать readAllLines из класса Files чтобы избежать этой неуклюжей конструкции. Этот метод служит удобным решением для чтения файлов, когда нам нужен построчно читаемый человеком вывод. Использование этого метода еще раз довольно просто и очень похоже на предыдущий пример (применяются те же ограничения):
Cras commodo, massa sed semper elementum, ligula orci malesuada tortor, sed iaculis ligula ligula et ipsum.
Чтение файла с использованием потоков
Переходя к более сложным подходам, мы всегда можем использовать старые добрые потоки, как это было в предыдущих версиях библиотеки. Поскольку это хорошо известное основание, я только покажу, как получить экземпляры этих потоков. Прежде всего, мы можем извлечь экземпляр InputStream из класса Files , вызвав метод newInputStream . Как обычно, можно дополнительно поиграть с шаблоном декоратора и сделать из него буферизованный поток. Или для удобства используйте метод newBufferedReader . Оба метода возвращают экземпляр потока, который является простым старым объектом java.io
Запись в файл
Запись в файл аналогична процессу чтения в ряде инструментов, предоставляемых библиотекой NIO.2, поэтому давайте просто рассмотрим:
- Запись байтового массива в файл
- Использование небуферизованных потоков
- Использование буферизованных потоков
Еще раз давайте сначала рассмотрим опцию байтового массива. Неудивительно, что у класса Files есть две версии метода write . Либо мы пишем байты из массива, либо из строк текста, нам необходимо сосредоточиться на StandardOpenOptions поскольку выбор этих модификаторов может зависеть от обоих методов. По умолчанию, когда метод StandardOpenOption не передается методу, метод write ведет себя так, как если бы присутствовали параметры CREATE , TRUNCATE_EXISTING и WRITE (как указано в Javadoc). Сказав это, пожалуйста, остерегайтесь использования версии метода write по умолчанию (без открытых опций), поскольку он либо создает новый файл, либо изначально обрезает существующий файл до нулевого размера. Файл автоматически закрывается по окончании записи — как после успешной записи, так и после возникновения исключения. Когда дело доходит до размеров файлов, применяются те же ограничения, что и в readAllBytes .
В следующем примере показано, как записать байтовый массив в файл. Обратите внимание на отсутствие какого-либо метода проверки из-за поведения метода write по умолчанию. Этот пример может быть запущен несколько раз с двумя разными результатами. Первый запуск создает файл, открывает его для записи и записывает байты из bytes массива в этот файл. Любой последующий вызов этого кода сотрет файл и запишет содержимое массива bytes в этот пустой файл. Оба запуска приведут к закрытому файлу с текстом «Hello world!» написано в первой строке.
Когда нам нужно писать строки вместо байтов, мы можем преобразовать строку в байтовый массив, однако есть и более удобный способ сделать это. Просто подготовьте список строк и передайте его для write метода. Обратите внимание на использование двух StandardOpenOption в следующем примере. Используя их для опций, я уверен, что файл присутствует (если он не существует, он создается) и способ добавить данные в этот файл (таким образом, не теряя ранее записанные данные). Весь пример довольно прост, посмотрите:
lines.add( «Cras commodo, massa sed semper elementum, ligula orci malesuada tortor, sed iaculis ligula ligula et ipsum.» );
Files.write(filePath, lines, StandardCharsets.UTF_8, StandardOpenOption.CREATE, StandardOpenOption.APPEND);
Запись в файл с использованием потоков
Может быть не очень хорошая идея работать с байтовыми массивами, когда дело касается больших файлов. Это когда приходят потоки. Подобно чтению главы, я не собираюсь объяснять потоки или как их использовать. Я бы предпочел сосредоточиться на способе получить их экземпляры. Class Files предоставляет метод newOutputStream который принимает StandardOpenOption для настройки поведения потоков. По умолчанию, когда метод StandardOpenOption не передается методу, метод write потоков ведет себя так, как если бы присутствовали параметры CREATE , TRUNCATE_EXISTING и WRITE (как указано в Javadoc). Этот поток не буферизуется, но с небольшим количеством магии декоратора вы можете создать экземпляр BufferedWriter . Чтобы противостоять этому неудобству, NIO.2 поставляется с методом newBufferWriter который сразу создает экземпляр буферизованного потока. Оба способа показаны в следующем фрагменте кода:
BufferedWriter writer = Files.newBufferedWriter(filePath2, StandardCharsets.UTF_8, StandardOpenOption.CREATE, StandardOpenOption.APPEND);
Копирование и перемещение файлов и каталогов
Копирование файлов и каталогов
Одной из наиболее полезных функций NIO.2 является обновленный способ обработки копирования и перемещения файлов и каталогов. Чтобы все было в порядке, дизайнеры решили OpenOption два родительских (маркерных) интерфейса в новый API файловой системы: OpenOption и CopyOption (оба интерфейса из пакета java.nio.file ). OpenOption StandardOpenOption упомянутое в предыдущей главе, реализует интерфейс OpenOption . Интерфейс CopyOption с другой стороны, имеет две реализации, одну из которых мы уже встречали в посте о ссылках в NIO.2 . Некоторые из вас могут вспомнить перечисление LinkOption которое называется методами управления реализацией, связанными с операциями, связанными со ссылками. Однако есть и другая реализация — перечисление StandardCopyOption из пакета java.nio.file . Еще раз, мы представляем еще одно перечисление — используемое для управления операциями копирования. Поэтому, прежде чем мы перейдем к какому-либо коду, рассмотрим, чего мы можем достичь, используя различные варианты копирования.
Значение | Описание |
---|---|
ATOMIC_MOVE | Переместите файл как элементарную операцию файловой системы. |
COPY_ATTRIBUTES | Скопируйте атрибуты в новый файл. |
REPLACE_EXISTING | Замените существующий файл, если он существует. |
Использование этих опций для управления операциями ввода-вывода довольно элегантно, а также просто. Поскольку мы пытаемся скопировать файл, ATOMIC_MOVE не имеет особого смысла в использовании (вы все равно можете его использовать, но в итоге вы получите java.lang.UnsupportedOperationException: Unsupported copy option ). Class Files предоставляет 3 варианта метода copy для разных целей:
- copy(InputStream in, Path target, CopyOption. options)
- Копирует все байты из входного потока в файл.
- Копирует все байты из файла в выходной поток.
- Скопируйте файл в целевой файл.
Прежде чем мы перейдем к какому-либо коду, я считаю, что полезно понять наиболее важные поведенческие особенности метода copy (последний вариант из трех выше). Метод copy ведет себя следующим образом (на основе Javadoc):
- По умолчанию копирование не выполняется, если целевой файл уже существует или является символической ссылкой.
- Если источник и цель — один и тот же файл, метод завершается без копирования файла. (для дальнейшей информации проверьте метод isSameFile класса Files )
- Атрибуты файла не обязательно копировать в целевой файл.
- Если исходный файл является каталогом, то он создает пустой каталог в целевом местоположении (записи в каталоге не копируются).
- Копирование файла не является атомарной операцией.
- Пользовательские реализации могут принести новые конкретные параметры.
Это были основные принципы внутренней работы метода copy . Сейчас самое время взглянуть на пример кода. Поскольку этот метод довольно прост в использовании, мы видим его в действии (используя наиболее распространенную форму метода copy ). Как и ожидалось, следующий код копирует исходный файл (и, возможно, перезаписывает целевой файл), сохраняя атрибуты файла: