Перейти к основному содержимому

Руководство по NIO2 FileVisitor

· 6 мин. чтения

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 .