走文件树

您是否需要创建一个将递归访问文件树中所有文件的应用程序?也许您需要删除树中的每个.class文件,或者查找去年未访问的每个文件。您可以使用FileVisitorinterface进行操作。

本节包括以下内容:

FileVisitor interface

要遍历文件树,您首先需要实现FileVisitorFileVisitor指定遍历过程中关键点的必需行为:访问文件时,访问目录之前,访问目录之后或失败时。该interface具有对应于这些情况的四种方法:

  • preVisitDirectory –在访问目录条目之前调用。

  • postVisitDirectory –访问目录中的所有条目后调用。如果遇到任何错误,则将特定的异常传递给该方法。

  • visitFile –调用了正在访问的文件。文件的BasicFileAttributes传递给该方法,或者您可以使用file attributes包读取一组特定的属性。例如,您可以选择读取文件的DosFileAttributeView,以确定文件是否设置了“隐藏”位。

  • visitFileFailed –无法访问文件时调用。特定的异常将传递给该方法。您可以选择是否引发异常,将其打印到控制台或日志文件,等等。

如果不需要实现所有FileVisitor方法,则可以扩展SimpleFileVisitor类,而不是实现FileVisitorinterface。此类实现FileVisitorinterface,访问树中的所有文件,并在遇到错误时抛出IOError。您可以扩展此类,并仅覆盖所需的方法。

这是扩展SimpleFileVisitor以打印文件树中所有条 Object 示例。无论条目是常规文件,符号链接,目录还是其他“未指定”类型的文件,它都会打印条目。它还打印每个文件的大小(以字节为单位)。遇到的任何异常都会打印到控制台。

FileVisitor方法以粗体显示:

import static java.nio.file.FileVisitResult.*;

public static class PrintFiles
    extends SimpleFileVisitor<Path> {

    // Print information about
    // each type of file.
    @Override
    public FileVisitResult visitFile(Path file,
                                   BasicFileAttributes attr) {
        if (attr.isSymbolicLink()) {
            System.out.format("Symbolic link: %s ", file);
        } else if (attr.isRegularFile()) {
            System.out.format("Regular file: %s ", file);
        } else {
            System.out.format("Other: %s ", file);
        }
        System.out.println("(" + attr.size() + "bytes)");
        return CONTINUE;
    }

    // Print each directory visited.
    @Override
    public FileVisitResult postVisitDirectory(Path dir,
                                          IOException exc) {
        System.out.format("Directory: %s%n", dir);
        return CONTINUE;
    }

    // If there is some error accessing
    // the file, let the user know.
    // If you don't override this method
    // and an error occurs, an IOException 
    // is thrown.
    @Override
    public FileVisitResult visitFileFailed(Path file,
                                       IOException exc) {
        System.err.println(exc);
        return CONTINUE;
    }
}

启动流程

实现FileVisitor后,如何启动文件遍历? Files类中有两个walkFileTree方法。

第一种方法只需要一个起点和FileVisitor的实例。您可以按以下方式调用PrintFiles文件访问者:

Path startingDir = ...;
PrintFiles pf = new PrintFiles();
Files.walkFileTree(startingDir, pf);

第二种walkFileTree方法使您可以另外指定访问级别的限制和一组FileVisitOption枚举。如果要确保此方法遍历整个文件树,则可以将Integer.MAX_VALUE指定为最大深度参数。

您可以指定FileVisitOption枚举FOLLOW_LINKS,它指示应遵循符号链接。

此代码段显示了如何调用四参数方法:

import static java.nio.file.FileVisitResult.*;

Path startingDir = ...;

EnumSet<FileVisitOption> opts = EnumSet.of(FOLLOW_LINKS);

Finder finder = new Finder(pattern);
Files.walkFileTree(startingDir, opts, Integer.MAX_VALUE, finder);

创建 FileVisitor 时的注意事项

文件树首先被深度遍历,但是您不能对访问子目录的迭代 Sequences 做任何假设。

如果您的程序将要更改文件系统,则需要仔细考虑如何实现FileVisitor

例如,如果要编写递归删除,则首先删除目录中的文件,然后再删除目录本身。在这种情况下,您将删除postVisitDirectory中的目录。

如果要编写递归副本,请先在preVisitDirectory中创建新目录,然后再try将文件复制到该目录中(在visitFiles中)。如果要保留源目录的属性(类似于 UNIX cp -p命令),则需要*在复制文件后的postVisitDirectory中执行此操作。 Copy示例显示了如何执行此操作。

如果要编写文件搜索,请使用visitFile方法执行比较。此方法查找符合您条件的所有文件,但找不到目录。如果要查找文件和目录,则还必须使用preVisitDirectorypostVisitDirectory方法执行比较。 Find示例显示了如何执行此操作。

您需要确定是否要遵循符号链接。例如,如果要删除文件,则不建议使用以下符号链接。如果要复制文件树,则可能要允许它。默认情况下,walkFileTree不跟随符号链接。

对文件调用visitFile方法。如果指定了FOLLOW_LINKS选项,并且文件树具有指向父目录的循环链接,则在visitFileFailed方法中使用FileSystemLoopException报告循环目录。以下代码段显示了如何catch圆形链接,该代码段来自Copy示例:

@Override
public FileVisitResult
    visitFileFailed(Path file,
        IOException exc) {
    if (exc instanceof FileSystemLoopException) {
        System.err.println("cycle detected: " + file);
    } else {
        System.err.format("Unable to copy:" + " %s: %s%n", file, exc);
    }
    return CONTINUE;
}

仅当程序跟随符号链接时,才会发生这种情况。

控制流程

也许您想要遍历文件树以查找特定目录,并且找到该目录后,您希望该过程终止。也许您想跳过特定目录。

FileVisitor方法返回FileVisitResult值。您可以中止文件遍历过程,也可以控制通过FileVisitor方法返回的值是否访问目录:

  • CONTINUE –表示应该 continue 走文件。如果preVisitDirectory方法返回CONTINUE,则访问目录。

  • TERMINATE –立即中止文件遍历。返回该值后,将不再调用其他文件遍历方法。

  • SKIP_SUBTREEpreVisitDirectory返回此值时,将跳过指定的目录及其子目录。该分支被“修剪”出树。

  • SKIP_SIBLINGS –当preVisitDirectory返回此值时,不会访问指定的目录,不会调用postVisitDirectory,也不会访问其他未访问的同级。如果从postVisitDirectory方法返回,则不会再访问任何同级对象。基本上,在指定目录中没有任何其他事件。

在此代码段中,将跳过名为SCCS的任何目录:

import static java.nio.file.FileVisitResult.*;

public FileVisitResult
     preVisitDirectory(Path dir,
         BasicFileAttributes attrs) {
    (if (dir.getFileName().toString().equals("SCCS")) {
         return SKIP_SUBTREE;
    }
    return CONTINUE;
}

在此代码段中,一旦找到特定文件,该文件名就会打印到标准输出,并且文件遍历将终止:

import static java.nio.file.FileVisitResult.*;

// The file we are looking for.
Path lookingFor = ...;

public FileVisitResult
    visitFile(Path file,
        BasicFileAttributes attr) {
    if (file.getFileName().equals(lookingFor)) {
        System.out.println("Located file: " + file);
        return TERMINATE;
    }
    return CONTINUE;
}

Examples

以下示例演示了文件遍历机制:

  • Find –递归文件树,以查找与特定 globPattern 匹配的文件和目录。此示例在Finding Files中讨论。

  • Chmod –递归更改文件树的权限(仅适用于 POSIX 系统)。

  • Copy –递归复制文件树。

  • WatchDir –演示了监视目录中已创建,删除或修改的文件的机制。使用-r选项调用该程序将监视整个树以进行更改。有关文件通知服务的更多信息,请参见监视目录以进行更改