A Guide To NIO2 FileVisitor – NIO2 FileVisitor指南

最后修改: 2016年 12月 3日

中文/混合/英文(键盘快捷键:t)

1. Overview

1.概述

In this article, we are going to explore an interesting feature of NIO2 – the FileVisitor interface.

在这篇文章中,我们将探讨NIO2的一个有趣的功能–文件访问器接口。

All operating systems and several third party applications have a file search function where a user defines search criteria.

所有的操作系统和一些第三方应用程序都有一个文件搜索功能,由用户定义搜索标准。

This interface is what we need to implement such a functionality in a Java application. Should you need to search for all .mp3 files, find and delete .class files or find all files that haven’t been accessed in the last month, then this interface is what you need.

这个接口是我们在Java应用程序中实现这种功能所需要的。如果你需要搜索所有的.mp3文件,找到并删除.class文件,或者找到所有在上个月没有被访问的文件,那么这个接口就是你所需要的。

All the classes we need to implement this functionality are bundled in one package:

我们需要实现这一功能的所有类都被捆绑在一个包里。

import java.nio.file.*;

2. How FileVisitor Works

2.FileVisitor如何工作

With the FileVisitor interface, you can traverse the file tree to any depth and perform any action on the files or directories found on any branch.

通过FileVisitor接口,你可以遍历文件树的任何深度,并对任何分支上发现的文件或目录执行任何操作。

A typical implementation of the FileVisitor interface looks like this:

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;
    }
}

The four interface methods allow us to specify the required behavior at key points in the traversal process: before a directory is accessed, when a file is visited, or when a failure occurs and after a directory is accessed respectively.

这四种接口方法允许我们在遍历过程中的关键点上指定所需的行为:在访问一个目录之前,在访问一个文件时,或者在失败发生时和访问一个目录之后分别指定所需的行为。

The return value at each stage is of type FileVisitResult and controls the flow of the traversal. Perhaps you want to walk the file tree looking for a particular directory and terminate the process when it is found or you want to skip specific directories or files.

每个阶段的返回值都是FileVisitResult类型,控制着遍历的流程。也许你想在文件树上寻找一个特定的目录,并在找到它时终止该进程,或者你想跳过特定的目录或文件。

FileVisitResult is an enum of four possible return values for the FileVisitor interface methods:

FileVisitResult是一个枚举,包含FileVisitor接口方法的四个可能的返回值。

  • FileVisitResult.CONTINUE – indicates that the file tree traversal should continue after the method returning it exits
  • FileVisitResult.TERMINATE – stops the file tree traversal and no further directories or files are visited
  • FileVisitResult.SKIP_SUBTREE – this result is only meaningful when returned from the preVisitDirectory API, elsewhere, it works like CONTINUE. It indicates that the current directory and all its subdirectories should be skipped
  • FileVisitResult.SKIP_SIBLINGS – indicates that traversal should continue without visiting the siblings of the current file or directory. If called in the preVisitDirectory phase, then even the current directory is skipped and the postVisitDirectory is not invoked

Finally, there has to be a way to trigger the traversal process, perhaps when the user clicks the search button from a graphical user interface after defining search criteria. This is the simplest part.

最后,必须有一种方法来触发遍历过程,也许是当用户在定义了搜索标准后,从图形用户界面上点击搜索按钮。这是最简单的部分。

We just have to call the static walkFileTree API of the Files class and pass to it an instance of Path class which represents the starting point of the traversal and then an instance of our FileVisitor:

我们只需调用Files类的静态walkFileTree API,并传递给它一个代表遍历起点的Path类的实例,然后传递给我们的FileVisitor的实例。

Path startingDir = Paths.get("pathToDir");
FileVisitorImpl visitor = new FileVisitorImpl();
Files.walkFileTree(startingDir, visitor);

3. File Search Example

3.文件搜索实例

In this section, we are going to implement a file search application using the FileVisitor interface. We want to make it possible for the user to specify the complete file name with extension and a starting directory in which to look.

在本节中,我们将使用FileVisitor接口实现一个文件搜索应用程序。我们想让用户能够指定完整的文件名和扩展名,以及要查找的起始目录。

When we find the file, we print a success message to the screen and when the entire file tree is searched without the file being found, we also print an appropriate failure message.

当我们找到该文件时,我们在屏幕上打印一个成功信息,当搜索整个文件树而没有找到该文件时,我们也会打印一个适当的失败信息。

3.1. The Main Class

3.1.主类

We will call this class FileSearchExample.java:

我们将这个类称为FileSearchExample.java

public class FileSearchExample implements FileVisitor<Path> {
    private String fileName;
    private Path startDir;

    // standard constructors
}

We are yet to implement the interface methods. Notice that we have created a constructor which takes the name of the file to search for and the path to start searching from. We will only use the starting path as a base case to conclude that the file has not been found.

我们还没有实现接口方法。请注意,我们已经创建了一个构造函数,它接收要搜索的文件名和要开始搜索的路径。我们将只使用起始路径作为基本情况,以断定文件未被找到。

In the following subsections, we will implement each interface method and discuss it’s role in this particular example application.

在下面的小节中,我们将实现每个接口方法,并讨论它在这个特定的应用实例中的作用。

3.2. The preVisitDirectory API

3.2.preVisitDirectory API

Let’s start by implementing the preVisitDirectory API:

让我们从实现preVisitDirectory API开始。

@Override
public FileVisitResult preVisitDirectory(
  Path dir, BasicFileAttributes attrs) {
    return CONTINUE;
}

As we say earlier, this API is called each time the process encounters a new directory in the tree. Its return value determines what will happen next depending on what we decide. This is the point at which we would skip specific directories and eliminate them from the search sample space.

正如我们前面所说,这个API在进程遇到树上的新目录时被调用。它的返回值决定了接下来会发生什么,取决于我们的决定。这就是我们要跳过特定的目录,将它们从搜索样本空间中剔除的时候。

Let’s choose not to discriminate any directories and just search in all of them.

让我们选择不歧视任何目录,只在所有的目录中搜索。

3.3. The visitFile API

3.3.visitFile API

Next, we will implement the visitFile API. This is where the main action happens. This API is called everytime a file is encountered. We take advantage of this to check the file attributes and compare with our criteria and return an appropriate result:

接下来,我们将实现visitFile API。这里是主要行动发生的地方。每当遇到一个文件,这个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;
}

In our case, we are only checking the name of the file being visited to know if it’s the one the user is searching for. If the names match, we print a success message and terminate the process.

在我们的例子中,我们只检查被访问的文件的名称,以了解它是否是用户正在搜索的文件。如果名称相符,我们就打印一个成功信息,并终止这个过程。

However, there is so much one can do here, especially after reading the File Attributes section. You could check created time, last modified time or last accessed times or several attributes available in the attrs parameter and decide accordingly.

然而,这里有很多事情可以做,尤其是在阅读了文件属性部分之后。你可以检查创建时间、最后修改时间或最后访问时间或attrs参数中的几个属性,并作出相应决定。

3.4. The visitFileFailed API

3.4.visitFileFailed API

Next, we will implement the visitFileFailed API. This API is called when a specific file is not accessible to the JVM. Perhaps it has been locked by another application or it could just be a permission issue:

接下来,我们将实现visitFileFailed API。当JVM无法访问某个特定文件时,就会调用这个API。也许它已经被另一个应用程序锁定,或者可能只是一个权限问题。

@Override
public FileVisitResult visitFileFailed(Path file, IOException exc) {
    System.out.println("Failed to access file: " + file.toString());
    return CONTINUE;
}

We simply log a failure message and continue with traversal of the rest of the directory tree. Within a graphical application, you could choose to ask the user whether to continue or not using a dialog box or just log the message somewhere and compile a report for later use.

我们只是简单地记录一个失败的消息,然后继续遍历目录树的其余部分。在一个图形化的应用程序中,你可以选择用一个对话框询问用户是否继续,或者只是把消息记录在某个地方,然后编写一份报告供以后使用。

3.5. The postVisitDirectory API

3.5.postVisitDirectory API

Finally, we will implement the postVisitDirectory API. This API is called each time a directory has been fully traversed:

最后,我们将实现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;
}

We use the Files.isSameFile API to check if the directory that has just been traversed is the directory where we started traversal from. If the return value is true, that means the search is complete and the file has not been found. So we terminate the process with a failure message.

我们使用Files.isSameFile API来检查刚刚被遍历的目录是否就是我们开始遍历的目录。如果返回值是true,这意味着搜索已经完成,文件没有被找到。所以我们用一个失败的消息来终止这个进程。

However, if the return value is false, that means we just finished traversing a subdirectory and there is still a probability of finding the file in some other subdirectory. So we continue with traversal.

然而,如果返回值是false,这意味着我们刚刚完成对一个子目录的遍历,仍然有可能在其他子目录中找到该文件。所以我们继续进行遍历。

We can now add our main method to execute the FileSearchExample application:

现在我们可以添加我们的主方法来执行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);
}

You can play around with this example by changing the values of startingDir and fileToSearch variables. When fileToSearch exists in startingDir or any of its subdirectories, then you will get a success message, else, a failure message.

你可以通过改变 startingDirfileToSearch 变量的值来玩这个例子。当 fileToSearch 存在于 startingDir 或其任何子目录中时,你将得到一个成功信息,否则,将得到一个失败信息。

4. Conclusion

4.结论

In this article, we have explored some of the less commonly used features available in the Java 7 NIO.2 filesystem APIs, particularly the FileVisitor interface. We have also managed to go through the steps of building a file search application to demonstrate its functionality.

在这篇文章中,我们探讨了Java 7 NIO.2文件系统API中的一些不太常用的功能,特别是FileVisitor接口。我们还设法通过构建一个文件搜索应用程序的步骤来演示其功能。

The full source code for the examples used in this article is available in the Github project.

本文中所使用的例子的完整源代码可在Github项目中找到。