Do you need to create an application that will recursively visit all the files in a file tree? Perhaps you need to delete every.class
file in a tree, or find every file that hasn't been accessed in the last year. This is easy with theFileVisitor
interface.This section covers:
- The FileVisitor Interface
- Kickstarting the Process
- Considerations when Creating a FileVisitor
- Controlling the Flow
- Examples
The FileVisitor Interface
To walk a file tree you first need to implement a
FileVisitor
. AFileVisitor
lays out the required behavior at key points in the traversal process: when visiting a file, before a directory is accessed, after a directory is accessed, or when failure occurs. The interface has five methods that correspond to these situations:
preVisitDirectory(T)
— invoked before a directory's entries are visited.preVisitDirectoryFailed(T, IOException)
— invoked when the directory could not be visited — perhaps the program does not have access to this portion of the file system. The specific error that was encountered is passed to the method as an exception. You can choose whether to throw the exception, print it to the console or a logfile, and so on.postVisitDirectory
— invoked after all the entries in a directory have been visited. If any errors were encountered, the specific exception is passed to the method.visitFile
— invoked on the file being visited. The file'sBasicFileAttributes
is passed to the method or you can use the file attributes package to read a specific set of attributes. For example, you may choose to read the file'sDosFileAttributeView
to determine if the file has the "hidden" bit set.visitFileFailed
— invoked when the file could not be accessed. The specific exception is passed to the method. You can choose whether to throw the exception, print it to the console or a logfile, and so on.If you don't need to implement all five of the
FileVisitor
methods, instead of implementing theFileVisitor
interface, you can extend theSimpleFileVisitor
class. This class, which implements theFileVisitor
interface, visits all files in a tree and throws an IOError when an error is encountered. You can extend this class and override only the methods that you require.Here is a simple example that extends
SimpleFileVisitor
to print all entries in a file tree. It prints out whether the entry is a regular file, a symbolic link, a directory, or some other "unspecified" type of file. It also prints the size, in bytes, of each file. Any exception that is encountered is printed to the console.The
FileVisitor
methods are bolded:
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 directory, let the user know. //If you don't override this method and an error occurs, an IOException //is thrown. @Override public FileVisitResult preVisitDirectoryFailed(Path dir, IOException exc) { System.err.println(exc); 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; } }
Kickstarting the Process
Once you have implemented your
FileVisitor
how do you get the party started? How do you initiate the file walk? There are twowalkFileTree
methods in theFiles
class. The first one requires only a starting point and an instance of yourFileVisitor
. You can invoke thePrintFiles
file visitor like this:
Path startingDir = ...; PrintFiles pf = new PrintFiles(); Files.walkFileTree(startingDir, pf);The second
walkFileTree
method allows you to additionally specify a limit on the number of levels visited and a set ofFileVisitOption
s. If you want to ensure that this method walks the entire file tree you can specifyInteger.MAX_VALUE
for the maximum depth argument.There are two
FileVisitOptions
you can specify:
FOLLOW_LINKS
— indicates that symbolic links should be followed.DETECT_CYCLES
— catches any circular references in the file tree caused by symbolic links that may point back to a parent directory.This code snippet shows how the four-argument method can be invoked:
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);
Considerations when Creating a FileVisitor
A file tree is walked depth first but you can't make any assumptions about the iteration order that subdirectories are visited.
If your program will be changing the file system, you need to carefully consider how you implement your file visitor.
For example, if you are writing a recursive delete, you first delete the files in a directory before deleting the directory itself. In this case, you delete the directory in
postVisitDirectory
.If you are writing a recursive copy, you will want to create the new directory in
preVisitDirectory
before attempting to copy the files to it (invisitFiles
). If you want to preserve the attributes of the source directory (similar to the UNIXcp -p
command), you need to do that after the files have been copied, inpostVisitDirectory
. Theexample is worth studying to see how this is done.
Copy
If you are writing a file search, you will perform the comparison in the
visitFile
method — this finds all the files that match your criteria but it will not find the directories. If you want to find both files and directories, you must also perform the comparison in either thepreVisitDirectory
orpostVisitDirectory
methods. Theexample shows how this can be done.
Find
You need to decide whether you want symbolic links to be followed. If you are deleting files, for example, following symbolic links may not be advisable. If you are copying a file tree, you may want to allow it. By default,
walkFileTree
does not follow symbolic links.Most of the time, the
visitFile
method is invoked for files. However, if you have specified the FOLLOW_LINKS option and your file tree has a circular link to a parent directory, that directory is passed tovisitFile
method — this is the only time a directory is passed to thevisitFile
method. In this event, you can detect the circular link in thevisitFile
method:
public FileVisitResult visitFile(Path file, BasicAttributes attrs) { //It's a circular reference! if (attrs.isDirectory()) { ... return CONTINUE; } //It's a file. ... return CONTINUE; }This scenario can occur only when following symbolic links.
Controlling the Flow
Perhaps you want to walk the file tree looking for a particular directory and, when found, you want the process to terminate. Perhaps you want to skip specific directories.
The
FileVisitor
methods return aFileVisitResult
value. You can abort the file walking process or control whether a directory is visited by the values you return in theFileVisitor
methods:
CONTINUE
— indicates the file walking should continue. If thepreVisitDirectory
method returnsCONTINUE
, the directory is visited.TERMINATE
— immediately aborts the file walking. No further file walking methods are invoked after this value is returned.SKIP_SUBTREE
— whenpreVisitDirectory
returns this value, the specified directory and its sub-directories are skipped — this branch is "pruned out" of the tree.SKIP_SIBLINGS
— whenpreVisitDirectory
returns this value, the specified directory is not visited,postVisitDirectory
is not invoked, and no further unvisited siblings are visited. If returned from thepostVisitDirectory
method, no further siblings are visited. Essentially, nothing further happens in the specified directory.In this code snippet, any directory named
SCCS
is skipped:
import static java.nio.file.FileVisitResult.*; public FileVisitResult preVisitDirectory(Path dir) { (if (dir.getName().toString().equals("SCCS")) { return SKIP_SUBTREE; } return CONTINUE; }In this code snippet, as soon as a particular file is located, the file name is printed to standard out and the file walking terminates:
import static java.nio.file.FileVisitResult.*; //The file we are looking for. Path lookingFor = ...; public FileVisitResult visitFile(Path file, BasicFileAttributes attr) { if (file.getName().equals(lookingFor)) { System.out.println("Located file: " + file); return TERMINATE; } return CONTINUE; }
Examples
There are several examples available that demonstrate the file walking mechanism:
— recurses a file tree looking for files and directories that match a particular glob pattern. This example is discussed in Finding Files.
Find
— recursively changes permissions on a file tree. (For POSIX systems only.)
Chmod
— recursively copies a file tree.
Copy
— demonstrates the mechanism that watches a directory for files that have been created, deleted or modified. Calling this program with the
WatchDir
-r
option watches an entire tree for changes. For more information on the file notification service, see Watching a Directory for Changes.