Java 8 файлы (NIO.2)

Цикл статей «Учебник Java 8».

Следующая статья — «Java 8 многопоточность».
Предыдущая статья — «Java 8 сериализация».

Содержание

java.nio.file.Path

Что такое Glob?

java.nio.file.Files

Проверка существования файла или каталога

Проверка прав доступа к файлу или каталогу

Один и тот же файл

Удаление файла или каталога

Копирование файла или каталога

Перемещение файла или каталога

Управление метаданными

Чтение, запись и создание файлов

— — OpenOption

— — Наиболее часто используемые методы для небольших файлов

— — Буферизированный ввод и вывод в текстовые файлы

— — Небуферезированный ввод и вывод

— — Создание файлов

— — Создание временных файлов

Файлы с произвольным доступом

Создание и чтение каталогов

— — Перечисление корневых каталогов файловой системы

— — Создание каталога

— — Создание временного каталога

— — Перечисление содержимого каталога

Символические и другие ссылки

— — Создание символических ссылок

— — Создание жёстких ссылок

— — Определение символической ссылки

— — Нахождение цели ссылки

Обход дерева файлов

— — Интерфейс java.nio.file.FileVisitor

— — Запуск процесса обхода дерева файлов

— — Размышления о FileVisitor

— — Управление обходом дерева файлов

— — Примеры

Поиск файлов

Подписываемся на изменения в каталоге

Обработка событий

java.nio.file.Path

Path  представляет из себя путь в файловой системе. Он содержит имя файла и список каталогов, определяющих путь к файлу.

Экземпляры Path  отражают путь в конкретной платформе (например /home/jho/foo  для Linux или C:\home\jho\foo  для Windows). Экземпляры Path  зависят от платформы. Нельзя сравнивать Path  из Linux с путём из Windows, даже если структура их каталогов одинаковая, и оба этих экземпляра указывают на один и тот же относительный файл.

Path может указывать на файл или каталог, которых не существует. Методы Path  работают только с представлением пути. Они не проверяют существование пути.

Создавать экземпляры Path  можно разными способами.

Можно использовать методы java.nio.file.Paths.get() :

Примеры:

Эти методы являются сокращённой формой для следующего кода:

Можно рассматривать Path  как класс, сохраняющий имена каталогов в пути и имя файла в виде последовательности. Наивысший (ближний к корневому) элемент находится по индексу 0, самый нижний элемент находится по индексу n-1 , где n  — количество элементов в пути.

Получить конкретный элемент пути можно с помощью метода getName() :

Узнать количество элементов в пути можно с помощью метода getNameCount():

Можно получить путь родительской директории с помощью метода getParent() :

Можно получить корень пути с помощью метода getRoot() :

С помощью метода toString()  можно получить путь в виде строки:

Многие файловые системы используют символ точки «.» для обозначения текущего каталога и две точки «..» для обозначения родительского каталога. Например:

Метод normalize()  удаляет все подобные элементы и приводит к нормализованному пути /home/jho/foo/. Этот метод не проверяет файловую систему. Это чисто синтетическая операция, работающая с элементами Path. Если sandy  является символической ссылкой, то удаление sandy/..  может привести к тому, что Path  больше не указывает на предыдущий файл.

Если вам нужно преобразовать Path  к строке, с помощью которой можно открыть  файл в браузере, то используйте метод toUri():

Метод toAbsolutePath()  преобразует путь к абсолютному. Способ преобразования зависит от системы.  Если переданный путь уже является абсолютным, то возвращается тот же самый объект Path.

Метод toRealPath()  возвращает реальный путь существующего файла. В качестве параметра в метод можно передать константу перечисления java.nio.file.LinkOption  с единственным возможным значением NOFOLLOW_LINKS . Метод бросает исключение, если файл не существует, либо к нему нет доступа. Этот метод убирает все элементы «.» и «..» и возвращает всегда абсолютный путь.

Можно объединять пути с помощью метода resolve() , в который передаётся часть пути, которую нужно добавить к исходному пути:

Или для Windows:

С помощью метода relativize()  можно создать относительный путь от одного пути к другому:

Слегка усложнённый пример:

Относительный путь не может быть получен только в том случае, когда только один из путей имеет корневой элемент. Если оба пути имеют корневой элемент, но возможность построения относительного пути зависит от системы.

Path  поддерживает метод equals(), что позволяет сравнивать пути. Так же есть методы startsWith(Path other) , startsWith(String other) , endsWith(Path other) , endsWith(String other) , позволяющие проверять начало и конец пути на совпадение с указанной строкой или частью пути. Path реализует интерфейс Comparable, что позволяет сортировать строки.

Что такое Glob?

Некоторые методы класса java.nio.file.Files  принимают аргумент glob. Шаблон glob использует следующие правила:

Символ «*» обозначает любое количество символов (включая отсутствие символов).

Две снежинки «**» работают так же, как и одна, но переходят за границы каталогов.

Символ вопроса «?» обозначает ровно один символ.

Фигурные скобки указывают коллекцию паттернов. Например {sun,moon,starts}  совпадает с "sun" , "moon"  или "starts". {temp*, tmp*}  совпадает со всеми строками, начинающимися с "temp"  или "tmp".

Квадратные скобки позволяют указать набор символов либо диапазон символов:

[aeiou] обозначает любую строчную гласную.

[0-9] обозначает любую цифру

[A-Z] обозначает любую прописную букву.

[a-z,A-Z] обозначает любую строчную или прописную букву.

Внутри квадратных скобок «*», «?» и «/» обозначают самих себя.

java.nio.file.Files

Проверка существования файла или каталога

Проверить существование пути Path  можно с помощью методов:

Если передать константу LinkOption.NOFOLLOW_LINKS , то метод не будет проходить по символическим ссылкам. Если оба метода exists()  и notExists()  возвращают false , то существование файла не может быть проверено (например нет доступа).

Проверка прав доступа к файлу или каталогу

Проверка доступа к файлу осуществляется с помощью методов:

Один и тот же файл

Можно проверить, что два пути указывают на один и тот же файл на одной и той же файловой системе:

Удаление файла или каталога

Метод delete(Path)  удаляет файл или бросает исключение, если удалить файл не удалось. Можно удалять каталог, но только если он пустой.

Метод

тоже удаляет файл, но не бросает никаких исключений, если файл не существует. Это может быть полезно, если два потока удаляют файл и вы не хотите получать исключение из-за того, что другой поток уже удалил файл до этого.

Копирование файла или каталога

Можно копировать файл или каталог с помощью метода copy(Path, Path, CopyOption...) , но имейте в виду, что файлы внутри каталога не копируются этим методом. Метод принимает константы CopyOption:

StandardCopyOption.REPLACE_EXISTING заменять существующие файлы.

StandardCopyOption.COPY_ATTRIBUTES копирует атрибуты файла.

LinkOption.NOFOLLOW_LINKS не переходить по символическим ссылкам.

Перемещение файла или каталога

Можно переместить файл или каталог с помощью метода move(Path, Path, CopyOption...) . Можно перемещать пустые каталоги. Возможность перемещения каталогов с содержимым зависит от платформы. Метод принимает CopyOption:

  • StandardCopyOption.REPLACE_EXISTING заменяет существующий файл, если он существует.
  • StandardCopyOption.ATOMIC_MOVE попытка осуществить перемещение файла как единую атомарную операцию. Все остальные опции игнорируются.

Управление метаданными

Метаданные файлов это: размер, дата создания, дата последнего изменения, владелец, права доступа и прочее.

Метаданные файлов и каталогов часто называют атрибутами файлов.

Методы для работы с метаданными:

 

Возвращает размер файла в байтах.

 

Проверяет, что path  указывает на каталог. Можно указать LinkOption.NOFOLLOW_LINKS , чтобы метод не переходил по символическим ссылкам.

 

Возвращает true, если path  указывает на обычный файл. Можно передать LinkOption.NOFOLLOW_LINKS, чтобы метод не переходил по символическим ссылкам.

 

Возвращает true , если path  указывает на символическую ссылку.

 

Возвращает true, если файл является скрытым. Для Linux файл является скрытым, если его имя начинается с точки. Для Windows файл является скрытым, если установлен соответствующий атрибут.

 

Возвращает FileTime с датой последнего изменения файла. Можно передать LinkOption.NOFOLLOW_LINKS , чтобы метод не переходил по символическим ссылкам.

 

Устанавливает дату последнего изменения файла. Смотрите описание класса FileTime в документации Oracle.

 

Возвращает владельца файла. Можно использовать метод String getName()  у возвращённого объекта, чтобы получить имя пользователя.

 

Меняет владельца файла.

 

Различные файловые системы имеют различные атрибуты файлов. Можно считывать группы атрибутов:

  • BasicFileAttributeView — базовые атрибуты файлов, которые должны поддерживаться всеми реализациями файловых систем.
  • DosFileAttributeView — расширяет стандартные атрибуты 4 битам (скрытый, архивный, только чтение, системный).
  • PosixFileAttributeView — расширяет базовые атрибуты атрибутами системы Linux.
  • FileOwnerAttributeView — поддерживается всеми файловыми системами, которые поддерживают владельцев файлов.
  • AclFileAttributeView — права доступа к файлу, которые реализованы в Windows.
  • UserDefinedFileAttributeView — пользовательские метаданные.

Получение конкретной группы атрибутов происходит с помощью метода

Пример:

Работа с конкретными группами метаданных/атрибутов очень зависит от платформы. Вам вряд ли когда-нибудь придётся столкнуться с этим. Но если интересно, то рекомендую рассмотреть мой проект niofilecommander, который использует все группы атрибутов, описанные здесь.

Чтение, запись и создание файлов

OpenOption

Многие методы, описанные в этом разделе используют OpenOption  в качестве своих параметров. В этом случае в метод можно передавать следующие значения:

  • LinkOption.NOFOLLOW_LINKS  не переходить по символическим ссылкам.
  • StandardOpenOption.APPEND  если файл открыт для записи, то байты будут добавляться в конец файла, а не в начало.
  • StandardOpenOption.CREATE  создавать новый файл, если его нет.
  • StandardOpenOption.CREATE_NEW  создавать новый файл. Если файл уже существует, то происходит ошибка.
  • StandardOpenOption.DELETE_ON_CLOSE  удалять файл при закрытии.
  • StandardOpenOption.DSYNC  каждое обновление содержимого файла синхронного пишется на устройство хранения (жёсткий диск).
  • StandardOpenOption.READ  открыть для чтения
  • StandardOpenOption.SPARCE  sparce file
  • StandardOpenOption.SYNC  каждое обновление содержимого файла или метаданных синхронно пишется на устройство чтения (жёсткий диск).
  • StandardOpenOption.TRUNCATE_EXISTING  если файл уже существует и открывается для записи, то его длина устанавливается в 0.
  • StandardOpenOption.WRITE  открывает файл на запись.

Наиболее часто используемые методы для небольших файлов

Читает содержимое файла и возвращает его в массиве байт.

 

Читает содержимое текстового файла и возвращает его в виде списка строк.

 

Записывает байты в файл.

 

Записывает строки в файл, преобразуя их в указанную кодировку.

 

Буферизированный ввод и вывод в текстовые файлы

 

Возвращает BufferedReader, который может быть использован для чтения из текстового файла.

 

Открывает или создаёт файл для записи. Возвращаемый BufferedWriter  может быть использован для записи в файл. Если OpenOption  не переданы, то используются опции: CREATE , TRUNCATE_EXISTING , WRITE.

Небуферезированный ввод и вывод

 

Возвращает поток, который можно использовать для чтения байт из файла.

 

Возвращает поток, который можно использовать для записи байт в файл. Если OpenOption  не переданы, то используются опции: CREATE, TRUNCATE_EXISTING, WRITE.

Создание файлов

Создаёт новый и пустой файл. Бросает исключение, если файл уже существует.

 

Создание временных файлов

Создаёт временный файл в указанном каталоге. Является только частью работы с временными файлами. Вы можете использовать метод File.deleteOnExit(), для того чтобы каталог удалялся автоматически.

 

Создаёт временный файл в специальном каталоге для временных файлов. Является только частью работы с временными файлами. Вы можете использовать метод File.deleteOnExit(), для того чтобы каталог удалялся автоматически.

Файлы с произвольным доступом

Файл можно открыть одновременно для чтения и записи и с возможностью перемещения текущей позиции в любое место файла. Подобная функциональность реализуется с помощью интерфейса java.nio.channels.SeekableByteChannel.

Получить экземпляр класса, реализующего интерфейс SeekableByteChannel, можно с помощью статического метода из класса Files:

либо:

Пример использования:

Создание и чтение каталогов

Перечисление корневых каталогов файловой системы

Для этого используется специальный метод класса FileSystem :

Пример:

Создание каталога

Метод класса Files :

Создаёт каталог.

 

Создаёт каталог вместе со всеми родительскими каталогами, которых ещё нет.

 

Создание временного каталога

Создаёт временный каталог. Как и методы createTempFile  является только частью работы с временными файлами. Вы можете использовать метод File.deleteOnExit(), для того чтобы каталог удалялся автоматически.

 

Создаёт временный каталог в специалном каталоге для временных файлов. Как и методы createTempFile  является только частью работы с временными файлами. Вы можете использовать метод File.deleteOnExit(), для того чтобы каталог удалялся автоматически.

 

Перечисление содержимого каталога

Возвращает поток, который позволяет пройтись по всем элементам каталога.

Пример использования:

 

Позволяет посмотреть элементы каталога, названия которых удовлетворяют переданному шаблону Glob.

Например, следующий код возвращает список файлов с расширениями «.class», «.java», «.jar»:

 

Можно написать свой собственный фильтр для содержимого каталога. Например, этот фильтр будет возвращать только каталоги:

Фильтр применяется методом:

Пример:

Символические и другие ссылки

Каждый метод класса Path  автоматически обрабатывает символические ссылки, либо предоставляет опции, которые позволяют указать, каким образом их обрабатывать.

Ссылки бывают символическими и жёсткими. Жёсткие ссылки имеют больше ограничений, чем символические:

  • Цель, на которую указывает жёсткая ссылка, должна существовать.
  • Жёсткие ссылки обычно не могут указывать на каталоги.
  • Жёсткие ссылки не могут указывать на другой раздел или том и не могут указывать на другую файловую систему.
  • Жёсткие ссылки выглядят и ведут себя так, как обычный файл, поэтому их может быть трудно обнаружить.
  • Жёсткая ссылка для всех целей и действий является той же сущностью, что и исходный файл. Они имеют те же самые права доступа, временные метки и т. д.

Из-за таких ограничений жёсткие ссылки не так часто используются, как символические ссылки.

Создание символических ссылок

Если ваша файловая система поддерживает символические ссылки, то вы можете создавать их с помощью метода класса Files:

Этот метод создаёт символическую ссылку по пути link, указывающую на target. Параметр attrs  позволяет задать атрибуты ссылки.

Создание жёстких ссылок

Создаёт жёсткую ссылку по пути link, указывающую на existing.

 

Определение символической ссылки

Этот метод класса Files  возвращает true, если файл является символической ссылкой.

 

Нахождение цели ссылки

Этот метод класса Files  возвращает путь, на который указывает символическая ссылка. Если link не является ссылкой, то бросается исключение java.nio.file.NotLinkException.

 

Обход дерева файлов

Интерфейс java.nio.file.FileVisitor

Чтобы обходить дерево файлов нужно сперва написать класс, реализующий интерфейс FileVisitor. Этот интерфейс позволяет описать действия, которые будут происходить в основные моменты обхода дерева файлов: при посещении файла, перед доступом к каталогу, после доступа к каталогу и при возникновении ошибки. FileVisitor  имеет четыре метода для этих ситуаций:

  • preVisitDirectory — вызывается перед посещением элементов каталога.
  • postVisitDirectory — вызывается после посещения всех элементов каталога.
  • visitFile — вызывается при посещении файла. В метод передаются BasicFileAttributes.
  • visitFileFailed — вызывается в случае, когда не удаётся получить доступ к файлу.

Если вам не нужны все эти четыре метода, то вы можете вместо реализации интерфейса FileVisitor  унаследоваться от java.nio.file.SimpleFileVisitor. Этот класс реализует FileVisitor  и посещает все файлы в дереве, бросая исключение IOException  при возникновении ошибки. Вы можете отнаследоваться от этого класса и реализовать только те методы, которые вам нужны.

Пример реализации SimpleFileVisitor:

Запуск процесса обхода дерева файлов

или

Второй метод позволяет задать максимальную глубину обхода и FileVisitOpion.FOLLOW_LINKS, что позволит заходить в символические ссылки.

Пример для нашего PrintFiles :

Размышления о FileVisitor

Дерево файлов обходится в глубину, но вы не можете предугадать порядок посещения подкаталогов.

Если ваша программа делает изменения в файловой системе, то вам нужно внимательно реализовывать ваш FileVisitor.

Например, если вы пишете рекурсивное удаление, то сначала вам нужно удалить файлы в каталоге, а затем удалят сам каталог. В этом случае удалять каталог нужно в методе postVisitDirectory.

Если вы пишете рекурсивное копирование, то вам сначала нужно создавать каталог в preVisitDirectory, а лишь потом копировать  файлы в visitFiles. Если вам нужно скопировать атрибуты с исходного каталога, то это нужно делать после копирования каталога, то есть в postVisitDirectory.

Если вы пишете поиск файла, то осуществляйте сравнение в visitFile, если вам нужно находить не только файлы, но и каталоги, то используйте в добавок метод preVisitDirectory  или postVisitDirectory.

Вам также нужно решить, как обрабатывать символические ссылки. Если вы удаляете файлы, то переходить по символическим ссылкам, скорее всего, не нужно. Если вы копируете дерево файлов, то вам потребуется переходить по ссылкам, скорее всего. По умолчанию метод walkFileTree  не переходит по символическим ссылкам.

Метод visitFile  вызывается для файлов. Если вы указали FOLLOW_LINKS, и ваше дерево файлов имеет циклическую ссылку на родительский каталог, то во время повторение каталога будет сообщено в visitFileFailed  с помощью исключения FileSystemLoopException.

Управление обходом дерева файлов

Во время обхода может потребоваться не заходить внутрь какого-либо каталога либо даже прервать обход дерева. Методы FileVisitor  возвращают java.nio.file.FileVisitResult, которое является перечислением со следующими константами:

  • FileVisitResult.CONTINUE  — указывает, что обход дерева файлов должен продолжаться. Если метод preVisitDirectory  возвращает CONTINUE , то каталог посещаяется.
  • FileVisitResult.TERMINATE  — прерывает обход всего дерева файлов.
  • FileVisitResult.SKIP_SUBTREE  — Если preVisitDirectory  возвращает это значение, то указанный каталог и его подкаталоги пропускаются.
  • FileVisitResult.SKIP_SIBLINGS  — Если preVisitDirectory  возвращает это значение, то указанный каталог не посещается, и postVisitDirectory  не вызывается. Если возвращается из postVisitDirectory, то остальные каталоги с тем же родительским каталогом пропускаются.

Примеры

Смотрите примеры в niofilecommander.

 

Поиск файлов

Каждая реализация файловой системы поставляет свою реализацию интерфейса  java.nio.file.PathMatcher.

Вы можете получить экземпляр класса, реализующий этот интерфейс для файловой системы вот так:

Строка, которая передаётся в этот метод, указывает какие файлы искать. В этом примере используется синтаксис glob. Можно использовать регулярные выражения, тогда вместо "glob:"  нужно писать "regex:".

В дальнейшем этот matcher  можно использовать при обходе дерева файлов вот так:

 

Подписываемся на изменения в каталоге

Последовательность шагов, которую нужно проделать, для того чтобы следить за изменениями в каком-нибудь каталоге:

  1. Создаём экземпляр WatchService  для файловой системы.
  2. Для каждого каталога, за которым нужно наблюдать, регистрируем наблюдателя (watcher). При регистрации каталога вы указываете события, о которых вам бы хотелось знать. Для каждого каталога вы получаете экземпляр класса WatchKey.
  3. Делаем бесконечный цикл на входящих событиях. При возникновении события WatchKey  этого каталога получает сигнал и кладётся в очередь наблюдателя.
  4. Получаем WatchKey  из очереди. Вы можете получить имя файла из этого ключа.
  5. Получаем каждое событие для ключа (их может быть несколько) и обрабатываем.
  6. Сбрасываем WatchKey  и возвращаемся в ожиданию событий.
  7. Закрываем WatchServce. Он закрывается при выходе из потоки либо при вызове метода.

Экземпляры WatchKey  потокобезопасны.

Пример:

Обработка событий

Сначала мы получаем WatchKey  с помощью одного из методов WatchService :

 

Удаляет из очереди и возвращает следующий WatchKey  либо null , если очередь пуста.

 

Удаляет из очереди и возвращает следующий WatchKey. Ждёт, если нужно, указанное количество времени.

 

Удаляет из очереди и возвращает следующий WatchKey. Если очередь пуста, то ждёт до тех пор, пока не появится хоть что-нибудь.

 

Затем обрабатываются все события  из WatchKey, которые получаются при вызове его метода:

 

Тип события получается с помощью следующего метода WatchEvent :

Не зависимо от событий, на которые мы подписаны, мы может получить событие OVERFLOW.

 

Получаем имя файла из события с помощью метода context() , который возвращает относительный путь к файлу от каталога, на изменения в котором мы подписаны.

 

Вызываем reset()  на WatchKey . Если он вернул false, то он больше не работает и из цикла можно выходить.

 

Пример обработки событий изменения содержимого каталога можно увидеть в niofilecommander, который обновляет свои панели при добавлении, переименовании и удалении файлов в них.

 
Цикл статей «Учебник Java 8».

Следующая статья — «Java 8 многопоточность».
Предыдущая статья — «Java 8 сериализация».

Один комментарий к “Java 8 файлы (NIO.2)”

Добавить комментарий

Ваш адрес email не будет опубликован. Обязательные поля помечены *