The Java Tutorials' Weblog

pageicon Tuesday Mar 24, 2009

Traversing a file tree in NIO.2

Have you downloaded JDK7 and played with NIO.2 yet? NIO.2 offers many new I/O features, particularly in the area of files and file system APIs. (To those of you who think, I can't use JDK7 because I am on a Mac: So am I. What I did was to download VirtualBox for my Mac (it's free). This allows you to set up a virtual environment on your machine. You can then download OpenSolaris, Ubuntu, or even Windows (but OpenSolaris and Ubuntu are free).

I wrote a simple example which you can use to find a file on your file system. (Think of a very simplified version of the Unix find.) The new java.nio.file.Files class provides a factory method, walkFileTree, you can use to traverse a tree of directories and files. When you invoke this method, you specify the root and how many levels deep you want to go. You can also set an attribute to indicate that it should follow links.

The meat of the work occurs in a class you create that implements the java.nio.file.FileVisitor interface. This interface has hooks for before, during, and after a file is "visited", as well as for when failure occurs. In my simple Find example, when the file is visited, the file name is compared to the user-specified name. If it matches, the full path is printed to stdout.

You create an instance of this class and pass it to the walkFileTree method. For each file or directory in the tree, the instance is invoked. You will notice that in this example, CONTINUE is returned in each of the FileVisitor methods. You can also return SKIP_SUBTREE or SKIP_SIBLINGS to terminate progress when necessary.

That's it.

If you download the NIO.2 JDK7 binaries, you will find other NIO.2 examples. Three of them also use the walkFileTree method: Chmod.java, Copy.java and WatchDir.java.

To call Find: java Find path file.

Here is the code for Find.java:

/**
 * Sample code that finds files.  Similar to the find(1) program, but simplified.
 */

public class Find {
    /**
     * A {@code FileVisitor} that finds a file.
     */
    static class Finder implements FileVisitor {
        private final Path findFile;

        Finder(Path findFile) {
            this.findFile = findFile;
        }

        void find(Path file) {
            if (file.getName().equals(findFile)) {
                System.out.format("%s%n", findFile);
            }
        }

        @Override
        public FileVisitResult visitFile(Path file, BasicFileAttributes attrs) {
            find(file);
            return CONTINUE;
        }

        @Override
        public FileVisitResult postVisitDirectory(Path dir, IOException exc) {
            return CONTINUE;
        }

        @Override
        public FileVisitResult visitFileFailed(Path file, IOException exc) {
            return CONTINUE;
        }

        @Override
        public FileVisitResult preVisitDirectory(Path dir) {
            find(dir);
            return CONTINUE;
        }

        @Override
        public FileVisitResult preVisitDirectoryFailed(Path dir, IOException exc) {
            return CONTINUE;
        }
    }

    static void usage() {
        System.err.println("java Find path file");
        System.exit(-1);
    }

    public static void main(String[] args) throws IOException {

        if (args.length < 2)
            usage();

        Path searchDir = Paths.get(args[0]);
        Path findFile = Paths.get(args[1]);

        // follow links when searching for files
        EnumSet<FileVisitOption> opts = EnumSet.of(FileVisitOption.FOLLOW_LINKS);
        Finder finder = new Finder(findFile);
        Files.walkFileTree(searchDir, opts, Integer.MAX_VALUE, finder);
    }
}

Update: This code has been slightly modified from when it was originally posted a few days ago. Originally, it compared the file names using the String class, but this may not provide the right result on some file systems. The comparison has been changed to the Path.equals method, which takes the type of file system into account.

Also, the example originally would only match files and not directories. By adding the comparison to the preVisitDirectory method, both files and directories will be examined.

-- Sharon Zakhour

Comments:

Sharon - good to see you are having fun with this. Note that find doesn't follow links by default so what you've implemented is closer to "find -follow" or "find -L". In any case, the important point about following links is cycles will be detected (and reported). This is important for operations like recursive copy where you need to follow links (for most other recursive operations you don't want to follow links). Another thing to mention is that the Path equals methods knows how to compare path based on the rules for the platform and so is more correct than String comparison. Alternatively, try out PathMatcher as that would allow you implement find's "-name <pattern>".

Posted by Alan on March 26, 2009 at 02:17 AM PDT #

Link to virtualbox should not have a quote at the end of it

Posted by Chris on March 26, 2009 at 08:19 AM PDT #

Thanks, Chris, I fixed the link.

Thanks for your comments, Alan. :)

Posted by Sharon Zakhour on March 26, 2009 at 02:37 PM PDT #

Post a Comment:
  • HTML Syntax: NOT allowed

« November 2009
SunMonTueWedThuFriSat
1
2
3
4
5
6
7
8
9
10
12
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
     
       
Today

Feeds

Search this blog

Links

Weblog menu

Today's referrers

Today's Page Hits: 160