Now, let’s talk about Input/Output, Files, and Streams.

Files & Directories

If you wanted to learn how to read and write files from Java, you might be tempted to look first at the class File.

It turns out that you do not use class File to read and write files, but it is a useful class that represents a file or a directory.   Here are some details about what it can do…

The File Class

The File class encapsulates the attributes and behavior needed to represent a file or a directory on the native system. The File class is not concerned with the contents of the file; it is concerned with the storage of the file on disk.

You can use an instance of the File class to perform certain operations on a corresponding file on the native system:

  • delete()
  • renameTo()
  • mkdir() and mkdirs()

Note that, while you can create a directory using the File class (mkdir()), you do not create a file through the File class. Creating a file is an operation performed by the stream classes, which we’ll discuss later.

Using Class File

Here’s an example of using File to rename a file:

package inputOutput;

import java.io.File;

public class RenameFile
{
    public static void main(String[] args)
    {
        File fromFile = new File("myFile.txt");
        File toFile   = new File("yourFile.txt");
        boolean renamed = fromFile.renameTo(toFile);
        if (renamed)
            System.out.println("File was successfully renamed");
        else
            System.out.println("File was not renamed");
    }
}

Note that no exception is thrown when the rename fails; you have to check the return value. The delete() method has the same return value convention.

Combining File Path and Name

If you wish to write portable Java code, do not do the following:

package inputOutput;

import java.io.File;

/**
 *  This class creates an instance of File in a non-portable way.
 */
public class FilePathName
{
    public static void main(String[] args)
    {
        File file = new File("path/path1/name.txt");
        
        // ...
    }
}

The above is UNIX-specific (although it will also work on Microsoft Windows). It can’t be assumed to work on other platforms.

Class File has the following, which you might think would help:

  • public static final String separator — set to the value of the System property file.separator
  • public static final char separatorChar — set to the value of the System property file.separator

(Note that File also has the following:

  • public static final String pathSeparator — set to the value of the System property path.separator
  • public static final char pathSeparatorChar — set to the value of the System property path.separator

but these relate to CLASSPATH separator characters, not file path separator characters.)

For example, you might think that something like:

package inputOutput;

import java.io.File;

/**
 *  This class creates an instance of File in a non-portable way.
 */
public class FilePathName
{
    public static void main(String[] args)
    {
        File file = new File("path" + File.separator +
                             "path1" + File.separator +
                             "name.txt");
        
        // ...
    }
}

would make things portable, but it doesn’t. There are some computer systems which do not use the pure file separator convention to separate directories from the files they contain.

How can we make things more portable? Well, take a look at the constructors available in the File class:

  • public File(String path)
  • public File(String path, String name)
  • public File(File dir, String name)

Let’s see how we can use these constructors to solve the portability problem (at least to the extent that we can):

package inputOutput;

import java.io.File;

/**
 *  This class creates an instance of File in a portable way.
 */
public class FilePathName
{
    public static void main(String[] args)
    {
        File path = new File("path");
        path = new File(path, "path1");
        path = new File(path, "name.txt");
        System.out.println("File path = '" + 
                           path.getAbsolutePath() + "'");
        
        // ...
    }
}

Notice how no explicit separators are used at all. If you type this in (or copy it from this page and paste it into an editor), you’ll find that the println produces a full (absolute) path specification (despite the fact that there is probably no such file on your system). Note that we entered a relative path specification.

What File Is Not

It is important to note that an instance of class File merely represents a file or a directory. Creating an instance of class File does not create a corresponding file on the native system. Further, an instance of class File may exist without there necessarily existing a corresponding file. However, using an instance of class File, you can discover a number of things about the corresponding file, including whether it exists or not:

  • canRead() and canWrite()
  • exists()
  • getName() and getPath()
  • isAbsolute()
  • getAbsolutePath() and getCanonicalPath()
  • isFile() and isDirectory()
  • lastModified()
  • length()

Listing Files & Directories

When an instance of File represents a directory, the list() methods:

  • list()
  • list(FilenameFilter filter)

may be used to obtain lists of files in that directory. Here’s an example:

package inputOutput;

import java.io.File;
import java.io.IOException;
import java.util.ArrayList;
import java.util.Iterator;
import java.util.Properties;

/**
 *  This class finds the current directory, gets a list of all
 *  the files/directories in the current directory, separates
 *  the files from the directories, and prints out the two lists.
 */
public class DirectoryLister
{
    public static void main(String[] args)
    {
        // Get current directory
        File dir = new File( System.getProperty("user.dir") );
        // Print out its contents
        printDirectoryContents(dir);
    }
    
    public static void printDirectoryContents(File dir)
    {
        if (!dir.isDirectory())
            return;
            
        try
        {
            System.out.println("Directory : '" + 
                                dir.getCanonicalPath() + "'");
            // Get the list of directory contents
            String[] contents = dir.list();
            // Go through the contents, separating out the files vs dirs.
            ArrayList<String> dirs  = new ArrayList<String>();
            ArrayList<String> files = new ArrayList<String>();
            for (int i = 0; i < contents.length; i++)
            {
                File file = new File(contents[i]);
                if (file.isFile())
                    files.add(contents[i]); // Add file name
                else if (file.isDirectory())
                    dirs.add(contents[i]); // Add dir name
            }
            // Print out the list of directories
            System.out.println("Directories:");
            Iterator<String> it = null;
            for (it = dirs.iterator(); it.hasNext(); )
            {
                String name = it.next();
                System.out.println(" " + name);
            }
            // Print out the list of files
            System.out.println("Files:");
            for (it = files.iterator(); it.hasNext(); )
            {
                String name = it.next();
                System.out.println(" " + name);
            }
        }
        catch (IOException e)
        {
            e.printStackTrace();
        }
    }
}

When I ran this program directly from within my NetBeans IDE, it gave me the following output:

Directory : '...\NetBeansProjects\InputOutput'
Directories:
 build
 dist
 nbproject
 src
 test
Files:
 .cvsignore
 build.xml
 manifest.mf

As expected, the above program only lists the directories and files in the top level directory.  That is, it does not recurse down into the directory hierarchy.

Listing Directory Contents with a Filter

The list(FilenameFilter filter) form may be used, in combination with an appropriate FilenameFilter, to produce a more selective list. For example, the list could be made to include only those files:

  • whose names begin with “foo”, or end in “bar”
  • whose sizes exceed or are smaller than a certain threshold
  • which can be written to
  • any combination of the above

FilenameFilter is an interface.  Its full definition is as follows:

public interface FilenameFilter 
{
    /**
     * Tests if a specified file should be included in a file list.
     *
     * @param dir the directory in which the file was found.
     * @param name the name of the file.
     * @return <code>true</code> if the name should be included in the file
     * list; <code>false</code> otherwise.
     * @since JDK1.0
     */
     boolean accept(File dir, String name);
}

Pretty simple, huh?

Note: There is a somewhat similar abstract class, javax.swing.filechooser.FileFilter, that relates (as you might expect, given the package) to Swing JFileChooser dialogs. It might be easy to confuse the FileFilter class with the FilenameFilter interface.

Here’s a modification of the previous example to provide filtering of the output so that only certain files are included.  In addition to filtering based on one or more file types, I’ve also added support for recursive directory searching (including the appropriate indentation so that the output represents the depth of recursion).  I’ve also removed the need to use any ArrayLists.

package inputOutput;

import java.io.File;
import java.io.FilenameFilter;
import java.io.IOException;
import java.util.Properties;

/**
 *  This class finds the current directory, gets a list of all
 *  the files/directories in the current directory, separates
 *  the files from the directories, and prints out the two lists.
 */
public class FilteredDirectoryLister
{
    public static void main(String[] args)
    {
        // Get current directory
        File dir = new File( System.getProperty("user.dir") );
        // Print out its contents, using a filter
        FileTypeFilter filter = new FileTypeFilter( 
                                      new String[] {".xml", ".properties"} );   
        printDirectoryContents(dir, filter, "");
    }
    
    public static void printDirectoryContents(File dir, 
                                              FileTypeFilter filter, 
                                              String indent)
    {
        if (!dir.isDirectory())
            return;
            
        try
        {
            System.out.println(indent + "Directory : '" + 
                                dir.getCanonicalPath() + "'");
            
            // Get the list of directories, using a directory filter
            String[] subdirs = dir.list(m_dirFilter);
            // Recurse through the subdirectories.
            for (String name : subdirs)
            {
              File subdir = new File(name);
              printDirectoryContents(subdir, filter, indent + " ");
            }
            
            // Now, get the list of matching files, using the file type filter
            String[] files = dir.list(filter);
            // Print out the list of files
            for (String name : files)
            {
                System.out.println(indent + " " + name);
            }
        }
        catch (IOException e)
        {
            e.printStackTrace();
        }
    }
    
    //// Private data ////
    private static DirectoryFilter m_dirFilter = new DirectoryFilter();
}

class FileTypeFilter implements FilenameFilter
{
    FileTypeFilter(String[] types)
    {
        m_types = types;
    }
    
    // Accepts only those files that have one of  
    // the supplied set of file types.
    public boolean accept(File dir, String name)
    {
        boolean includeFile = false;
        File path = new File(dir, name);
        if (path.isFile())
        {
          for (String type : m_types)
          {
            if (name.endsWith(type))
            {
              includeFile = true;
              break;
            }
          }
        }
        return includeFile;
    }
    
    //// Private data ////
    private String[] m_types;
}

class DirectoryFilter implements FilenameFilter
{
    // Accepts any directory
    public boolean accept(File dir, String name)
    {
        boolean includeFile = false;
        File path = new File(dir, name);
        if (path.isDirectory())
          includeFile = true;
        return includeFile;
    }
}

When I ran this from within my NetBeans IDE, it gave me the following output:

Directory : '...\NetBeansProjects\InputOutput'
 Directory : '...\NetBeansProjects\InputOutput\build'
 Directory : '...\NetBeansProjects\InputOutput\dist'
 Directory : '...\NetBeansProjects\InputOutput\nbproject'
  build-impl.xml
  genfiles.properties
  project.properties
  project.xml
 Directory : '...\NetBeansProjects\InputOutput\src'
 Directory : '...\NetBeansProjects\InputOutput\test'
 build.xml

Now, we do see files and directories in the entire hierarchy, and only those files that match the specified filetypes.