Traversing the file system

Traversing the file system is one of the common tasks required in applications.  Search the web for examples and you can find plenty of examples of how to go about it.  One of the most popular is to work through the file system using recursive calls like this:

package softwarepulse.io;

import java.io.File;

/**
 * @author jmcneil
 * (c) copyright Software Pulse 2018
 *
 */
public class WalkDirectory1 {

	/**
	 * @param args
	 */
	public static void main(String[] args) {
		 File[] files = new File("C:/").listFiles();
	     showFiles(files);
	}

    public static void showFiles(File[] files) {
        for (File file : files) {
            if (file.isDirectory()) {
                System.out.println("Directory: " + file.getName());
                showFiles(file.listFiles()); // Calls same method again.
            } else {
                System.out.println("File: " + file.getName());
            }
        }
    }
}

 

Now the beauty of this code is that it is clean compact and elegant but not without issue.

While the recursive call solution gets the job done some feel that the recursive nature of the solution can be problematic.  Repeatedly calling the same function can blow the stack, so if you have a large file system to work through there is the possibility your application will crashing.

That said, for most applications that is not an issue, so if you are looking for something that can work through a file system then the recursive call solution will get the job done.

Now there was a reason I was looking at working through a file system.  That reason was that I was building an application where I wanted to walk through a file system.  However, the solution presented earlier did not meet my needs.  You see the recursive call solution navigates the file system depth first.  What that means is given the following directory structure:

Illustrating traversing the file directory structure depth first.

The code would process files in the directories in the order numbered from 1 to 7.  As you can see the recursive call method works its way down the structure and then across.

My particular requirement was to process all the files in the top level directory, then all the files in the directories below the top directory before moving on to the directories below them.  So this is the order in which I wanted to process the directories.

Illustrating traversing the file directory structure breadth first.

The solution was based on the recursive call solution but ironically the changes resulted in a non-recursive call.

This new solution is not as elegant, compact or I suspect as fast however, it does deliver the solution I was looking for.  The general approach is this:

Given a starting point within a file system, say the root directory, make a File object from the String containing the path and add this to an ArrayList object.  Then set up a FOR loop to iterate through all the items in the ArrayList.  Initially this will only be the single item we have added.  A check is made to see if the File object is a directory and again for the first item this should be true.  If not true then we go on to process the next item in the ArrayList but if true a call is made to a method called showFiles and the File object is passed as a parameter.  The showFile method will return an array of Files all of which will be file system directories.  These directories are added to the ArrayList and the FOR loop upper limit is adjusted to take into account the addition of extra items.

The showFiles method is where all the files in the current directory are identified and processed.  Now in my case I’m only interested in image files so I use a Filter class to obtain only those files of the type I’m interested in.  Another FOR loop processes each of these files in turn.

Once all the files are processed, I then use an inner class to create a directory filter to allow all the directories within the current directory to be returned.  This is then returned to the invoking method.

One item of note is that there are instances where a Null value may be return.  This would need to be handled to prevent an error in the main method.  The code for the WalkDirectory2 class looks like this:

package softwarepulse.io;

import java.io.File;
import java.io.FileFilter;
import java.util.ArrayList;
import java.util.Arrays;

/**
 * @author jmcneil
 * (c) copyright Software Pulse 2018
 *
 */
public class WalkDirectory2 {

	/**
	 * @param args
	 */
	public static void main(String[] args) {
		 //File[] files = new File("C:/").listFiles();
        File file = new File("C:/");
        ArrayList<File> files = new ArrayList<File>();
        files.add(file);
 
        int size = files.size();
 
        for( int i = 0; i < size; i++) {
            System.out.println("Directory: " + files.get(i).getAbsolutePath());
            if(files.get(i).isDirectory()) {
                files.addAll(Arrays.asList(showFiles(files.get(i))));
                size = files.size();
            }
        }
	}

    public static File[] showFiles(File file) {
    	 
        // Get all the files for the root path
        try {
            File[] imgs = file.listFiles(new FileNameFilterOption());
            for(File img : imgs) {
                System.out.println("File: " + img.getName());
            }
 
        // Get all the Dirs for the root path
        FileFilter directoryFilter = new FileFilter() {
            public boolean accept(File file) {
                return file.isDirectory();
            }
        };
 
        File[] files = file.listFiles(directoryFilter);
        return files;
        } catch(NullPointerException npe) {
            System.err.println(npe.getCause());
            return null;
        }
    }
}

 

The FileNameFilterOption class implements FilternameFilter and gives an example of how to filter for specific file types.  Note that if the operating system is case sensitive you will need to handle this.  As you can see from the code I have simply added both upper and lower case to the filter.  This is not an ideal solution but demonstrates the point.   A more elegant solution would be to allow the filter to be set using a properties file or if there is no requirement to have a static implementation then add a method to set a filter property.

So the code for FilenameFilerOptions look like this:

package softwarepulse.io;

import java.io.File;
import java.io.FilenameFilter;

/**
 * @author jmcneil
 * (c) copyright Software Pulse 2018
 *
 */
public class FileNameFilterOption implements FilenameFilter {
	 
		@Override
	    public boolean accept(File dir, String filename) {
	 
	        // File extensions to look for, hard coded for now but should be parameterised or configurable
	        String[] ext = {"png", "PNG", "gif", "GIF", "bmp", "BMP", "jpg", "JPG"};
	 
	        for (int i = 0; i < ext.length; i++) {
	            if(filename.endsWith(ext[i])) {
	                return true;
	            }
	        }
	        return false;
	    }
}

 

And that’s it, a way of traversing through the file system and processing all the files in order of how deep they are from the starting point.  Also an example of how to use filtering to process only those files or interest.

If you would like to download the source code for this example click here (email address required)

One final point, with the introduction of Java version 8 and perhaps even version 7 there are now alternative ways to navigate through the file system, I have gone with this method as I am supporting Java version 6 and above.

Leave a Reply

Your email address will not be published.

This site uses Akismet to reduce spam. Learn how your comment data is processed.