Java 教程是为 JDK 8 编写的。本页中描述的示例和实践未利用在后续版本中引入的改进。
你是否需要创建一个递归访问文件树中所有文件的应用程序?也许你需要删除树中的每个 .class
文件,或查找去年未访问过的每个文件。你可以使用 FileVisitor
接口执行此操作。
本节包括以下内容:
要遍历文件树,首先需要实现 FileVisitor
。FileVisitor
指定遍历过程中关键点所需的行为:访问文件时,访问目录之前,访问目录之后,或发生故障时。该接口有四种方法对应于这些情况:
preVisitDirectory
–在访问目录条目之前调用。postVisitDirectory
–访问目录中的所有条目后调用。如果遇到任何错误,则将特定异常传递给该方法。visitFile
–在被访问的文件上调用。文件的 BasicFileAttributes
将传递给方法,或者你可以使用 file attributes 包来读取一组特定的属性。例如,你可以选择读取文件的 DosFileAttributeView
以确定文件是否设置了“隐藏”位。visitFileFailed
–无法访问文件时调用。特定异常传递给该方法。你可以选择是抛出异常,将其打印到控制台或日志文件,等等。如果你不需要实现所有四个 FileVisitor
方法,则不需要实现 FileVisitor
接口,可以继承 SimpleFileVisitor
类。此类实现 FileVisitor
接口,访问树中的所有文件,并在遇到错误时抛出 IOError
。你可以继承此类并仅覆盖所需的方法。
下面是一个继承 SimpleFileVisitor
的示例,用于打印文件树中的所有条目。它打印条目不管条目是常规文件,符号链接,目录还是其他“未指定”类型的文件。它还会打印每个文件的大小(以字节为单位)。遇到的任何异常都会打印到控制台。
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
。
例如,如果你正在编写递归删除,则首先删除目录中的文件,然后再删除目录本身。在这种情况下,你在 postVisitDirectory
中删除目录。
如果你正在编写递归复制,则在 preVisitDirectory
中创建新目录,然后将文件复制到目录中(在 visitFiles
方法中)。如果要保留源目录的属性(类似于 UNIX cp -p
命令),则需要在复制文件之 后 的 postvisitdirectory
中执行该操作。
示例显示了如何执行此操作。Copy
如果你正在编写文件搜索,则在 visitFile
方法中执行比较。此方法查找符合条件的所有文件,但不查找目录。如果要查找文件和目录,还必须在 preVisitDirectory
或 postVisitDirectory
方法中执行比较。
示例显示了如何执行此操作。Find
你需要决定是否要遵循符号链接。例如,如果要删除文件,则可能不建议遵循符号链接。如果要复制文件树,则可能需要遵循它。默认情况下,walkFileTree
不遵循符号链接。
为文件调用 visitFile
方法。如果已指定 FOLLOW_LINKS
选项,并且文件树具有指向父目录的循环链接,则循环目录将在 visitFileFailed
方法中被报告,通过 FileSystemLoopException
。以下代码段显示了如何捕获循环链接,并且来自
示例: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
- 表示文件遍历应继续。如果 preVisitDirectory
方法返回 CONTINUE
,则访问该目录。TERMINATE
- 立即中止文件遍历。返回此值后,不会调用其他文件遍历方法。SKIP_SUBTREE
- 当 preVisitDirectory
返回此值时,将跳过指定的目录及其子目录。这个分支被“修剪”出树。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; }
以下示例演示了文件遍历机制:
Find
–递归文件树,查找与特定 glob 模式匹配的文件和目录。在 Finding Files 中讨论了此示例。Chmod
–递归更改文件树的权限(仅适用于 POSIX 系统)。Copy
–递归复制文件树。WatchDir
–演示监听目录以查找创建,删除或修改的文件的机制。使用 -r
选项调用此程序会监听整个树的更改。有关文件通知服务的详细信息,请参阅 Watching a Directory for Changes。