1. Обзор
В этой статье мы собираемся изучить интересную особенность NIO2 — интерфейс FileVisitor
.
Все операционные системы и несколько сторонних приложений имеют функцию поиска файлов, где пользователь определяет критерии поиска.
Этот интерфейс — то, что нам нужно для реализации такой функциональности в Java-приложении. Если вам нужно найти все файлы .mp3
, найти и удалить файлы .class
или найти все файлы, к которым не обращались за последний месяц, то этот интерфейс — то, что вам нужно.
Все классы, которые нам нужны для реализации этой функциональности, объединены в один пакет:
import java.nio.file.*;
2. Как работает FileVisitor
С интерфейсом FileVisitor
вы можете перемещаться по дереву файлов на любую глубину и выполнять любые действия с файлами или каталогами, найденными в любой ветви.
Типичная реализация интерфейса FileVisitor
выглядит так:
public class FileVisitorImpl implements FileVisitor<Path> {
@Override
public FileVisitResult preVisitDirectory(
Path dir, BasicFileAttributes attrs) {
return null;
}
@Override
public FileVisitResult visitFile(
Path file, BasicFileAttributes attrs) {
return null;
}
@Override
public FileVisitResult visitFileFailed(
Path file, IOException exc) {
return null;
}
@Override
public FileVisitResult postVisitDirectory(
Path dir, IOException exc) {
return null;
}
}
Четыре метода интерфейса позволяют нам указать требуемое поведение в ключевых точках процесса обхода: до доступа к каталогу, при посещении файла или при возникновении сбоя и после доступа к каталогу соответственно.
Возвращаемое значение на каждом этапе имеет тип FileVisitResult
и управляет потоком обхода. Возможно, вы хотите пройтись по дереву файлов в поисках определенного каталога и завершить процесс, когда он будет найден, или вы хотите пропустить определенные каталоги или файлы.
FileVisitResult
— это перечисление четырех возможных возвращаемых значений для методов интерфейса FileVisitor
:
FileVisitResult.CONTINUE
— указывает, что обход дерева файлов должен продолжаться после выхода возвращающего его метода.FileVisitResult.TERMINATE
— останавливает обход дерева файлов и больше не посещает каталоги или файлыFileVisitResult.SKIP_SUBTREE
— этот результат имеет смысл только при возврате изAPI preVisitDirectory
, в других местах он работает какCONTINUE
. Указывает, что текущий каталог и все его подкаталоги следует пропустить.FileVisitResult.SKIP_SIBLINGS
— указывает, что обход должен продолжаться без посещения братьев и сестер текущего файла или каталога. Есливызывается на этапе preVisitDirectory
, то даже текущий каталог пропускается, аpostVisitDirectory
не вызывается
Наконец, должен быть способ инициировать процесс обхода, например, когда пользователь нажимает кнопку поиска
в графическом пользовательском интерфейсе после определения критериев поиска. Это самая простая часть.
Нам просто нужно вызвать статический API walkFileTree класса
Files
и передать ему экземпляр класса Path
, который представляет начальную точку обхода, а затем экземпляр нашего FileVisitor
:
Path startingDir = Paths.get("pathToDir");
FileVisitorImpl visitor = new FileVisitorImpl();
Files.walkFileTree(startingDir, visitor);
3. Пример поиска файлов
В этом разделе мы собираемся реализовать приложение для поиска файлов с использованием интерфейса FileVisitor
. Мы хотим, чтобы пользователь мог указать полное имя файла с расширением и начальный каталог для поиска.
Когда мы находим файл, мы выводим на экран сообщение об успехе, а когда выполняется поиск по всему дереву файлов, но файл не найден, мы также выводим соответствующее сообщение об ошибке.
3.1. Основной класс
Мы назовем этот класс FileSearchExample.java
:
public class FileSearchExample implements FileVisitor<Path> {
private String fileName;
private Path startDir;
// standard constructors
}
Нам еще предстоит реализовать методы интерфейса. Обратите внимание, что мы создали конструктор, который принимает имя файла для поиска и путь, с которого начинается поиск. Мы будем использовать начальный путь только в качестве базового случая, чтобы сделать вывод, что файл не найден.
В следующих подразделах мы реализуем каждый метод интерфейса и обсудим его роль в этом конкретном примере приложения.
3.2. API preVisitDirectory
_
Начнем с реализации preVisitDirectory
API:
@Override
public FileVisitResult preVisitDirectory(
Path dir, BasicFileAttributes attrs) {
return CONTINUE;
}
Как мы уже говорили ранее, этот API вызывается каждый раз, когда процесс встречает новый каталог в дереве. Его возвращаемое значение определяет, что произойдет дальше, в зависимости от того, что мы решим. Это точка, в которой мы пропустим определенные каталоги и исключим их из пространства выборки поиска.
Давайте решим не различать какие-либо каталоги и просто искать во всех них.
3.3. API визита
_
Далее мы реализуем API visitFile
. Здесь происходит основное действие. Этот API вызывается каждый раз при обнаружении файла. Мы воспользуемся этим, чтобы проверить атрибуты файла и сравнить их с нашими критериями и вернуть соответствующий результат:
@Override
public FileVisitResult visitFile(Path file, BasicFileAttributes attrs) {
String fileName = file.getFileName().toString();
if (FILE_NAME.equals(fileName)) {
System.out.println("File found: " + file.toString());
return TERMINATE;
}
return CONTINUE;
}
В нашем случае мы проверяем только имя посещаемого файла, чтобы узнать, тот ли это файл, который ищет пользователь. Если имена совпадают, мы печатаем сообщение об успехе и завершаем процесс.
Однако здесь так много можно сделать, особенно после прочтения раздела « Атрибуты файла
». Вы можете проверить время создания, время последнего изменения или время последнего доступа или несколько атрибутов, доступных в параметре attrs
, и принять соответствующее решение.
3.4. API VisitFileFailed
_
Далее мы реализуем API visitFileFailed
. Этот API вызывается, когда определенный файл недоступен для JVM. Возможно, он был заблокирован другим приложением или это может быть просто проблема с разрешением:
@Override
public FileVisitResult visitFileFailed(Path file, IOException exc) {
System.out.println("Failed to access file: " + file.toString());
return CONTINUE;
}
Мы просто регистрируем сообщение об ошибке и продолжаем обход остальной части дерева каталогов. В графическом приложении вы можете спросить пользователя, следует ли ему продолжать или нет, используя диалоговое окно, или просто запишите сообщение где-нибудь и скомпилируйте отчет для последующего использования.
3.5. API postVisitDirectory
_
Наконец, мы реализуем postVisitDirectory
API. Этот API вызывается каждый раз, когда каталог полностью пройден:
@Override
public FileVisitResult postVisitDirectory(Path dir, IOException exc){
boolean finishedSearch = Files.isSameFile(dir, START_DIR);
if (finishedSearch) {
System.out.println("File:" + FILE_NAME + " not found");
return TERMINATE;
}
return CONTINUE;
}
Мы используем Files.isSameFile
API, чтобы проверить, является ли только что пройденный каталог тем каталогом, с которого мы начали обход. Если возвращаемое значение равно true
, это означает, что поиск завершен и файл не найден. Таким образом, мы завершаем процесс с сообщением об ошибке.
Однако, если возвращаемое значение равно false
, это означает, что мы только что закончили обход подкаталога, и все еще есть вероятность найти файл в каком-то другом подкаталоге. Итак, продолжаем обход.
Теперь мы можем добавить наш основной метод для выполнения приложения FileSearchExample
:
public static void main(String[] args) {
Path startingDir = Paths.get("C:/Users/user/Desktop");
String fileToSearch = "hibernate-guide.txt"
FileSearchExample crawler = new FileSearchExample(
fileToSearch, startingDir);
Files.walkFileTree(startingDir, crawler);
}
Вы можете поиграть с этим примером, изменив значения переменных startDir
и fileToSearch
. Когда fileToSearch
существует в начальном
каталоге или любом из его подкаталогов, вы получите сообщение об успешном выполнении, иначе сообщение об ошибке.
4. Вывод
В этой статье мы рассмотрели некоторые из менее часто используемых функций, доступных в API-интерфейсах файловой системы Java 7 NIO.2, в частности интерфейс FileVisitor
. Нам также удалось пройти этапы создания приложения для поиска файлов, чтобы продемонстрировать его функциональность.
Полный исходный код примеров, использованных в этой статье, доступен в проекте Github .