Section 10.2
Files
THE DATA AND PROGRAMS IN A COMPUTER'S MAIN MEMORY
survive only as long as the power is on. For more permanent storage,
computers use files, which are
collections of data stored on a hard disk, on a
floppy disk, on a CD-ROM, or on some other type of storage device.
Files are organized into directories
(sometimes called "folders"). A directory can hold
other directories, as well as files. Both directories and
files have names that are used to identify them.
Programs can read data from existing files. They can create
new files and can write data to files. In Java, such input and output
is done using streams.
Human-readable character data is read from a file using
an object belonging to the class FileReader,
which is a subclass of Reader. Similarly,
data is written to a file in human-readable format
through an object of type FileWriter, a subclass
of Writer. For files that store data in machine
format, the appropriate I/O classes are FileInputStream
and FileOutputStream. In this section, I will only discuss
character-oriented file I/O using the
FileReader and FileWriter classes.
However, FileInputStream and FileOutputStream
are used in an exactly parallel fashion. All these classes
are defined in the java.io package.
It's worth noting right at the start that applets which are
downloaded over a network connection are generally not allowed
to access files. This is a security consideration. You can
download and run an applet just by visiting a Web page with
your browser. If downloaded applets had access to the files on
your computer, it would be easy to write an applet that would
destroy all the data on a computer that downloads it. To
prevent such possibilities, there are a number of things that
downloaded applets are not allowed to do. Accessing files is
one of those forbidden things. Standalone programs written
in Java, however, have the same access to your files as any
other program. When you write a standalone Java application,
you can use all the file operations described in this section.
The FileReader class has a constructor which
takes the name of a file as a parameter and creates an input
stream that can be used for reading from that file. This
constructor will throw an exception of type FileNotFoundException
if the file doesn't exist. This exception type requires mandatory
exception handling, so you have to call the constructor in
a try statement (or inside a routine that is declared to
throw FileNotFoundException). For example, suppose
you have a file named "data.txt", and you want
your program to read data from that file. You could do the
following to create an input stream for the file:
FileReader data; // (Declare the variable before the
// try statement, or else the variable
// is local to the try block and you won't
// be able to use it later in the program.)
try {
data = new FileReader("data.txt"); // create the stream
}
catch (FileNotFoundException e) {
... // do something to handle the error -- maybe, end the program
}
The FileNotFoundException class is a subclass of
IOException, so it would be acceptable to catch
IOExceptions in the above try...catch statement.
More generally, just about any error that can occur during
input/output operations can be caught by a catch clause
that handles IOException.
Once you have successfully created a FileReader,
you can start reading data from it. But since FileReaders
have only the primitive input methods inherited from the
basic Reader class, you will probably want to
wrap your FileReader in a
TextReader object or in some other wrapper class.
(The TextReader class is not a standard part of Java;
it is described in the previous section.)
To create a TextReader for reading from a
file named data.dat, you could say:
TextReader data;
try {
data = new TextReader(new FileReader("data.dat"));
}
catch (FileNotFoundException e) {
... // handle the exception
}
Once you have a TextReader named data,
you can read from it using such methods as data.getInt()
and data.getWord(), exactly as you would from any
other TextReader.
Working with output files is no more difficult than this.
You simply create an object belonging to the class
FileWriter. You will probably want to wrap
this output stream in an object of type PrintWriter.
For example, suppose you want to write data to a file named
"result.dat". Since the constructor for
FileWriter can throw an exception of type
IOException, you should use a try statement:
PrintWriter result;
try {
result = new PrintWriter(new FileWriter("result.dat"));
}
catch (IOException e) {
... // handle the exception
}
If no file named result.dat exists, a new file will be
created. If the file already exists, then the current contents of the
file will be erased and replaced with the data that your program
writes to the file. An IOException might occur if,
for example, you are trying to create a file on a disk that
is "write-protected," meaning that it cannot be
modified.
After you are finished using a file, it's a good idea to
close the file, to tell the
operating system that you are finished using it. (If you forget
to do this, the file will ordinarily be closed automatically
when the program terminates or when the file stream object
is garbage collected, but it's best to close a file as soon
as you are done with it.) You can close a file by
calling the close() method of the associated
stream. Once a file has been closed, it
is no longer possible to read data from it or
write data to it, unless you open it again as a new stream.
(Note that for most stream classes, the close()
method can throw an IOException, which must be
handled; however, both PrintWriter and
TextReader override this method so that
it cannot throw such exceptions.)
As a complete example, here is a program that will read
numbers from a file named data.dat, and will then write
out the same numbers in reverse order
to another file named result.dat. It is assumed
that data.dat contains only one number on each line,
and that there are no more than 1000 numbers altogether.
Exception-handling is used to check for problems along the
way. Although the application is not a particularly useful one,
this program demonstrates the basics of working with files.
(By the way, at the end of this program, you'll find our first example of
a finally clause in a try
statement. When the computer executes a try
statement, the commands in its finally clause are
guaranteed to be executed, no matter what.)
import java.io.*;
// (The TextReader class must also be available to this program.)
public class ReverseFile {
public static void main(String[] args) {
TextReader data; // Character input stream for reading data.
PrintWriter result; // Character output stream for writing data.
double[] number = new double[1000]; // An array to hold all
// the numbers that are
// read from the file.
int numberCt; // Number of items actually stored in the array.
try { // Create the input stream.
data = new TextReader(new FileReader("data.dat"));
}
catch (FileNotFoundException e) {
System.out.println("Can't find file data.dat!");
return; // End the program by returning from main().
}
try { // Create the output stream.
result = new PrintWriter(new FileWriter("result.dat"));
}
catch (IOException e) {
System.out.println("Can't open file result.dat!");
System.out.println(e.toString());
data.close(); // Close the input file.
return; // End the program.
}
try {
// Read the data from the input file.
numberCt = 0;
while (data.eof() == false) { // Read until end-of-file.
number[numberCt] = data.getlnDouble();
numberCt++;
}
// Output the numbers in reverse order.
for (int i = numberCt-1; i >= 0; i--)
result.println(number[i]);
System.out.println("Done!");
}
catch (TextReader.Error e) {
// Some problem reading the data from the input file.
System.out.println("Input Error: " + e.getMessage());
}
catch (IndexOutOfBoundsException e) {
// Must have tried to put too many numbers in the array.
System.out.println("Too many numbers in data file.");
System.out.println("Processing has been aborted.");
}
finally {
// Finish by closing the files,
// whatever else may have happened.
data.close();
result.close();
}
} // end of main()
} // end of class
File Names, Directories, and the File Class
The subject of file names is actually more complicated than I've let
on so far. To fully specify a file, you have to give both the name
of the file and the name of the directory where that file is located.
A simple file name like "data.dat" or "result.dat"
is taken to refer to a file in a directory that is called the
current directory (or "default
directory" or "working directory"). The current
directory is not a permanent thing. It can be changed by the
user or by a program. Files not in the current directory must
be referred to by a path name, which
includes both the name of the file and information about the
directory where it can be found.
To complicate matters even further,
there are two types of path names, absolute
path names and relative path names.
An absolute path name uniquely identifies one file among all the files
available to the computer. It contains full information about which
directory the file is in and what its name is. A relative path
name tells the computer how to locate the file, starting from the
current directory.
Unfortunately, the syntax for file names and path names varies
quite a bit from one type of computer to another. Here are some
examples:
- data.dat -- on any computer, this would be a file named
data.dat in the current directory.
- /home/eck/java/examples/data.dat -- This is an absolute path
name in the UNIX operating system. It refers to a file named data.dat
in a directory named examples, which is in turn in a directory
named java,....
- C:\eck\java\examples\data.dat -- An absolute path name on a
DOS or Windows computer.
- Hard Drive:java:examples:data.dat -- Assuming that "Hard Drive"
is the name of a disk drive, this would be an absolute path name on a
computer using Macintosh OS 9.
- examples/data.dat -- a relative path name under UNIX. "Examples"
is the name of a directory that is contained within the current directory,
and data.data is a file in that directory. The corresponding
relative path names for Windows and Macintosh would be examples\data.dat
and examples:data.dat.
Similarly, the rules for determining which directory is the current
directory are different for different types of computers. It's reasonably
safe to say, though, that if you stick to using simple file names only,
and if the files are stored in the same directory with the program that
will use them, then you will be OK.
To avoid some of the problems caused by differences between platforms,
Java has the class java.io.File. An object belonging to this
class represents a file. More precisely, an object of type
File represents a file name rather than a file as such.
The file to which the name refers might or might not exist. Directories
are treated in the same way as files, so a File object
can represent a directory just as easily as it can represent a file.
A File object has a constructor new File(String)
that creates a File object from a path name. The name can be a simple
name, a relative path, or an absolute path. For example, new File("data.dat")
creates a File object that refers to a file named data.dat, in the current directory.
Another constructor,
new File(File,String), has two parameters. The first is
a File object that refers to the directory that contains the file.
The second is the name of the file. Later in this section, we'll look at
a convenient way of letting the user specify a File in a GUI program.
File objects contain several useful instance methods. Assuming that
file is a variable of type File, here are some of the
methods that are available:
file.exists() -- This boolean-valued
function returns true if the file named by the File object already
exists. You could use this method if you wanted to avoid overwriting the
contents of an existing file when you create a new FileWriter.
file.isDirectory() -- This boolean-valued
function returns true if the File object refers to a directory. It
returns false if it refers to a regular file or if no file with the given name
exists.
file.delete() -- Deletes the file, if it
exists.
file.list() -- If the File object
refers to a directory, this function returns an array of type String[]
containing the names of the files in that directory. Otherwise, it returns null.
Here, for example, is a program that will list the names of all the files
in a directory specified by the user:
import java.io.File;
public class DirectoryList {
/* This program lists the files in a directory specified by
the user. The user is asked to type in a directory name.
If the name entered by the user is not a directory, a
message is printed and the program ends.
*/
public static void main(String[] args) {
String directoryName; // Directory name entered by the user.
File directory; // File object referring to the directory.
String[] files; // Array of file names in the directory.
TextIO.put("Enter a directory name: ");
directoryName = TextIO.getln().trim();
directory = new File(directoryName);
if (directory.isDirectory() == false) {
if (directory.exists() == false)
TextIO.putln("There is no such directory!");
else
TextIO.putln("That file is not a directory.");
}
else {
files = directory.list();
TextIO.putln("Files in directory \"" + directory + "\":");
for (int i = 0; i < files.length; i++)
TextIO.putln(" " + files[i]);
}
} // end main()
} // end class DirectoryList
All the classes that are used for reading data from files and writing data
to files have constructors that take a File object as a parameter.
For example, if file is a variable of type File, and
you want to read character data from that file, you can create a
FileReader to do so by saying new FileReader(file).
If you want to use a TextReader to read from the file, you might
use:
TextReader data;
try {
data = new TextReader( new FileReader(file) );
}
catch (FileNotFoundException e) {
... // handle the exception
}
File Dialog Boxes
In many programs, you want the user to be able to
select the file that is going to be used
for input or output. If your program lets the user type in the
file name, you will just have to assume that the user understands how
to work with files and directories. But in a graphical user interface,
the user expects to be able to select files using a
file dialog box, which is a special
window that a program can open when it wants the user to select
a file for input or output. Swing includes a platform-independent
technique for using file dialog boxes in the form of a class called
JFileChooser. This class is part of the package
javax.swing. We looked at some basic dialog boxes
in Section 7.7. File dialog
boxes are similar to those, but are a little more complicated
to use.
A file dialog box shows the user a list of files and sub-directories
in some directory, and makes it easy for the user to specify a file
in that directory. The user can also navigate easily from one
directory to another. The most common constructors for JFileChooser
specify the directory that is selected when the dialog box first appears:
new JFileChooser( File startDirectory )
new JFileChooser( String pathToStartDirectory )
There is also a constructor with no arguments that will set the user's
home directory to be the starting directory in the dialog box.
(The constructor call new JFileChooser(".") produces
a dialog box that has the current directory as its starting directory.
This is true since "." is a
special path name that refers to the current directory,
at least on Windows and UNIX systems.)
Constructing a JFileChooser object does not make
the dialog box appear on the screen. You have to call a
methods in the object to do that. There are two different methods
that can be used because there are two types of file dialog:
An open file dialog
allows the user to specify an existing file to be
opened for reading data into the program; a save file dialog
lets the user specify a file, which might or might not already exist,
to be opened for writing data from the program. File dialogs of these
two types are opened using the showOpenDialog and showSaveDialog
methods.
A file dialog box always has a parent, another
component which is associated with the dialog box. The parent is specified
as a parameter to the showOpenDialog or showSaveDialog methods.
The parent is a GUI component, and can usually be specified as "this".
(The parameter can be null, in which case an invisible component
is used as the parent.) Both showOpenDialog and showSaveDialog
have a return value, which will be one of the constants JFileChooser.CANCEL_OPTION
JFileChooser.ERROR_OPTION or JFileChooser.APPROVE_OPTION.
If the return value is JFileChooser.APPROVE_OPTION, then the user
has selected a file. If the return value is something else, then the user
did not select a file. The user might have clicked a "Cancel" button,
for example. You should always check the return value, to make sure that
the user has, in fact, selected a file. If that is the case, then you
can find out which file was selected by calling the JFileChooser's
getFile() method, which returns an object of type File
that represents the selected file.
Putting all this together, typical code for using a JFileChooser
to read character data from a file looks like this:
JFileChooser fileDialog = new JFileChooser(".");
int option = fileDialog.showOpenDialog(this);
if (option == JFileChooser.APPROVE_OPTION) {
File selectedFile = fileDialog.getFile();
try {
TextReader data = new TextReader(new FileReader(selectedFile));
}
catch (FileNotFoundException e) {
// Handle the error.
}
.
. // Read data from the file.
.
}
The first line creates a new JFileChooser object in which
the current directory is initially selected. The second line shows
the file dialog box on the screen and waits for the user to select
a file or close the dialog box in some other way. The third line
tests whether the user has actually selected a file. Only in that
case do we proceed to get the selected file, open it, and use it.
Writing data to a file would be similar, but showSaveDialog
would replace showOpenDialog.
There is nothing to stop you, by the way, from using the same
JFileChooser object over and over. This would have the
advantage that the selected directory would be remembered from one
use to the next.
It's common to do some configuration of a JFileChooser before
calling showOpenDialog or showSaveDialog. For example,
the instance method setDialogTitle(String) can be used to specify
a title to appear at the top of the window. And setSelectedFile(File)
can be used to set the file that is selected in the dialog box when it
appears. This can be used to provide a default file choice for the user.
We'll look at some more complete examples of using files and file
dialogs in the next section.