After that long series on memory leaks, it’s time to take a break and look at something simpler.
Like most people, I’ve accumulated a lot of files on my hard disks. We all need to look through them occasionally and delete unnecessary files. I could have simply done a file listing through a file manager or on the command line, but I decided to automate the process with a small Java program.
The java.nio.file Package
The java.nio.file
package has been available since Java 1.7. It contains interfaces and classes to access file systems, files and file attributes.
There is some interoperability between classes in the java.nio.file
package and the java.io
package. The java.io.File
class has a toPath()
method. This converts the abstract path represented by a File
object to a java.nio.file.Path
object. Both the original File
object and the Path
object can operate on the same physical file.
Which Class to Use?
When looking through the java.nio.file
package for a class to list files, the most obvious starting point is the Files
class. This class consists of lots of static methods that operate on files and directories. In general, these methods delegate the actual work to the file system itself.
Four methods are possible candidates for listing all the files in a directory: find()
, list()
, walk()
and walkFileTree()
. The first three return a lazily populated Stream
of Path
entries, i.e. Stream<Path>
. The walkFileTree()
method takes a more traditional file walking approach.
Let’s use the traditional walkFileTree()
method first to list files.
The walkFileTree() Method
The walkFileTree()
method walks a file tree starting at a given directory. It traverses the file tree, calling the methods of a FileVisitor
object for each file it comes across. This is a typical implementation of the Gang of Four (GoF) Visitor Design Pattern.
For a recap on the pattern, read my two posts: Visitor Design Pattern – Part One and Visitor Design Pattern – Part Two.
There are two overloaded walkFileTree()
methods:
public static Path walkFileTree(Path start,
Set<FileVisitOption> options,
int maxDepth,
FileVisitor<? super Path> visitor)
throws IOException
public static Path walkFileTree(Path start,
FileVisitor<? super Path> visitor)
throws IOException
Both methods take a starting Path
specification and a FileVisitor
object.
The second method calls the first method with defaults for two of the parameters as follows:
walkFileTree(start,
EnumSet.noneOf(FileVisitOption.class),
Integer.MAX_VALUE,
visitor)
The parameter EnumSet.noneOf(FileVisitOption.class)
tells it not to follow symbolic links. The Integer.MAX_VALUE
parameter tells it to visit all levels of the file tree.
FileVisitor Interface
The FileVisitor
interface has four methods that we must implement. I’ve omitted the parameters and return values for clarity.
preVisitDirectory()
is called on a directory before the directory entries are visited.postVisitDirectory()
is called on a directory after all the directory entries and their descendants have been visited.visitFile()
is called on each file in that directory.visitFileFailed()
is called if a file could not be visited. This can happen if the file attributes can’t be read or a directory can’t be opened, etc.
Remember that the walkFileTree()
method walks the file tree, and calls the appropriate FileVisitor
methods for each file it comes across.
For ease of coding, we can extend SimpleFileVisitor
instead of implementing the FileVisitor
interface. This class has default method behaviour. Unless we override them, all the methods return FileVisitResult.CONTINUE
and/or re-throw any I/O exceptions.
Example
Here’s an example of a SimpleFileVisitor
class. It prints the current directory, the number of files in it and the total size of the files (in bytes).
import java.io.IOException;
import java.nio.file.Path;
import java.nio.file.FileVisitResult;
import java.nio.file.SimpleFileVisitor;
import java.nio.file.attribute.BasicFileAttributes;
import static java.nio.file.FileVisitResult.CONTINUE;
public class CountFiles extends SimpleFileVisitor<Path> {
private int fileCount = 0;
private long fileSizeTotal = 0L;
private final String fmt =
"Directory: %s contains %d files; total = %,14d bytes%n";
// Increments the fileCount and the fileSizeTotal.
@Override
public FileVisitResult visitFile(Path file,
BasicFileAttributes attr) {
++fileCount;
fileSizeTotal += attr.size();
return CONTINUE;
}
// Resets the fileCount and fileSizeTotal for each new directory.
@Override
public FileVisitResult preVisitDirectory(Path dir,
BasicFileAttributes attrs) {
fileCount = 0;
fileSizeTotal = 0L;
return CONTINUE;
}
// Print each directory visited with fileCount and fileSizeTotal.
@Override
public FileVisitResult postVisitDirectory(Path dir, IOException ex) {
System.out.printf(fmt, dir, fileCount, fileSizeTotal);
return CONTINUE;
}
// If there is an problem accessing a file, print the exception.
// The default behaviour is to only throw an IOException.
@Override
public FileVisitResult visitFileFailed(Path file, IOException ex) {
System.err.println(ex);
return CONTINUE;
}
} // end of class
Here is the main program that kicks off the file walker:
import java.io.File;
import java.io.IOException;
import java.nio.file.Files;
import java.nio.file.FileSystems;
import java.nio.file.Path;
public class VisitingFileWalker {
public static void main(String args[]) throws IOException {
Path startDir;
startDir = FileSystems.getDefault().getPath("c://<path>");
// OR
// startDir = new File("c://<path>").toPath();
// the visitor
CountFiles counter = new CountFiles();
// the walker
Files.walkFileTree(startDir, counter);
} // end of main
} // end of class
If you’d like to experiment a bit with the code, try adding the following lines to the visitFile()
method. You can also call other BasicFileAttributes
methods such as size()
, creationTime()
, lastAccessTime()
, and lastModifiedTime()
.
if (attr.isSymbolicLink()) {
System.out.printf("Symbolic link: %s ", file);
} else if (attr.isRegularFile()) {
System.out.printf("Regular file: %s ", file);
} else {
System.out.printf("Other: %s ", file);
}
System.out.printf("(%,14d bytes)%n", attr.size());
Summary
As we can see, the traditional file walking approach using a visitor is simple and easy to understand. Next week we’ll look at a Stream
based approach using Files.walk()
.
Have you written a file walker? Does this make it easier for the next time you need to write one? I’d love to hear your comments.
Stay safe and keep learning!