Walking a File Tree in Java

Stylised image of a man walking along a road

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!

Leave a Comment

Your email address will not be published. Required fields are marked *

Code like a Java Guru!

Thank You

We're Excited!

Thank you for completing the form. We're excited that you have chosen to contact us about training. We will process the information as soon as we can, and we will do our best to contact you within 1 working day. (Please note that our offices are closed over weekends and public holidays.)

Don't Worry

Our privacy policy ensures your data is safe: Incus Data does not sell or otherwise distribute email addresses. We will not divulge your personal information to anyone unless specifically authorised by you.

If you need any further information, please contact us on tel: (27) 12-666-2020 or email info@incusdata.com

How can we help you?

Let us contact you about your training requirements. Just fill in a few details, and we’ll get right back to you.

Your Java tip is on its way!

Check that incusdata.com is an approved sender, so that your Java tips don’t land up in the spam folder.

Our privacy policy means your data is safe. You can unsubscribe from these tips at any time.