Java File I/O
Java provides several classes for working with files. Different classes have different jobs: some represent the location of a file, some read or write text, and others read or write binary data. These classes are often connected together so that one object provides access to the file while another provides the specific operations needed by the program.
Exceptions and File I/O
Many file operations can fail for reasons outside the program's control. A requested file might not exist, the program might not have permission to access it, or another error might occur while reading or writing.
Because of this, many Java File I/O operations can produce checked exceptions.
One way to deal with an exception is to catch it:
try {
// File operation
} catch (IOException exception) {
// Respond to the problem
}
However, an exception does not always have to be caught in the method where it occurs. A method can instead declare the exception using throws:
public static String readData() throws IOException {
// File operation
}
In this case, responsibility for the exception is passed to the method that called readData(). Catching an exception and allowing an exception to propagate with throws are both valid ways of dealing with checked exceptions.
The File Class
A File object represents the location or path of a file or directory.
For example:
File file = new File("words.txt");
Creating this object does not open words.txt and does not load the contents of the file into memory. The File object simply represents the path.
A File can be used to ask questions about a file, such as whether it exists, whether it is a file or directory, and where it is located.
For example:
File file = new File("words.txt");
boolean exists = file.exists();
String absolutePath = file.getAbsolutePath();Relative and Absolute Paths
The path:
words.txt
is a relative path. Its location is interpreted relative to the program's current working directory.
An absolute path describes a location beginning from the root of the file system rather than from the program's current directory.
An absolute path should not be confused with a unique path. More than one path can sometimes refer to the same physical file. For example, path components such as . and .., symbolic links, and other file-system features can allow different absolute paths to ultimately identify the same file.
A canonical path is a normalized form intended to remove these kinds of path differences.
Reading Text with Scanner
The Scanner class can be used to read text from a file.
A common pattern is to first create a File object and then give that object to a Scanner:
File file = new File("words.txt");
Scanner in = new Scanner(file);
This can also be written as one statement:
Scanner in = new Scanner(new File("words.txt"));
Once the Scanner is connected to the file, normal Scanner methods can be used:
String word = in.next();
String line = in.nextLine();
There is an important difference between these two statements:
Scanner in = new Scanner(new File("words.txt"));
and
Scanner in = new Scanner("words.txt");
In the first version, the Scanner reads from the file named words.txt.
In the second version, the Scanner reads from the String itself. It would therefore scan the characters in "words.txt" rather than opening a file with that name.
Writing Text with PrintWriter
PrintWriter is useful for writing text to a file.
For example:
PrintWriter out = new PrintWriter(new File("results.txt"));
The familiar print() and println() methods can then write text:
out.println("File I/O Example");
PrintWriter also provides printf(), which allows formatted text to be written.
out.printf("Name: %s%n", name);
If the PrintWriter is connected to a file, the formatted text produced by printf() is written to that file.
Writers should be closed when the program is finished with them. Closing a writer also makes sure that any remaining output is sent to its destination.
Try-With-Resources
Objects that work with files should normally be closed after they are no longer needed. Forgetting to close a resource can cause problems and may leave files or operating-system resources open unnecessarily.
Java provides try-with-resources to make this easier.
A resource is created inside the parentheses following try:
try (Scanner in = new Scanner(new File("words.txt"))) {
String word = in.next();
}
When execution leaves the try block, Java automatically closes the Scanner.
Try-with-resources works with objects that implement the AutoCloseable interface. Many Java I/O classes implement this interface.
More than one resource can be created in the same try-with-resources statement. The resources are separated by semicolons:
try (
Scanner in = new Scanner(new File("input.txt"));
PrintWriter out = new PrintWriter(new File("output.txt"))
) {
out.println(in.nextLine());
}
When the block finishes, both resources are automatically closed.
This makes try-with-resources the preferred way to manage many Java File I/O objects.
Binary Files
Text files store information as characters that people can generally read in a text editor.
Binary files store information as bytes in a format determined by the program. Java provides several stream classes for working with binary files.
FileInputStream and FileOutputStream
A FileInputStream provides a stream of bytes coming from a file.
A FileOutputStream provides a stream of bytes going to a file.
These streams often form the first layer of a binary I/O operation. Another stream can then be placed around them to provide additional capabilities.
This technique is sometimes called wrapping streams.
DataInputStream
A DataInputStream can wrap another input stream:
DataInputStream in =
new DataInputStream(new FileInputStream(new File("data.bin")));
DataInputStream adds methods for reading Java primitive data values from a binary stream.
Examples include:
int value = in.readInt();
double measurement = in.readDouble();
boolean available = in.readBoolean();
It can also read specially encoded strings using methods such as readUTF().
A DataInputStream is intended for primitive data values. It does not provide the general-purpose object deserialization performed by ObjectInputStream.
If complete Java objects need to be reconstructed from a binary file, Java uses an ObjectInputStream instead.
ObjectOutputStream
An ObjectOutputStream is used when a program wants to write Java objects to a binary stream.
Like other specialized streams, it is normally wrapped around another stream. To write objects to a file, an ObjectOutputStream can wrap a FileOutputStream:
ObjectOutputStream out =
new ObjectOutputStream(
new FileOutputStream(
new File("stuff.bin")));
The layers have separate responsibilities:
ObjectOutputStream
↓
FileOutputStream
↓
File
The File identifies the location.
The FileOutputStream provides a byte stream connected to that file.
The ObjectOutputStream adds the ability to convert Java objects into a form that can be written to the stream.
An object can then be written using:
out.writeObject(object);
Serializable Objects
Java cannot automatically write every possible object using ObjectOutputStream.
Objects written using object serialization must be serializable. A class indicates that its objects can be serialized by implementing the Serializable interface:
public class Student implements Serializable {
// Class contents
}
Serializable is a marker interface. A class does not have to implement special methods simply because it implements Serializable. Instead, the interface tells Java that instances of the class are allowed to participate in Java object serialization.
In general, the objects that make up the object's stored state must also be serializable.
Choosing the Appropriate I/O Class
The important idea is that Java uses different classes for different File I/O responsibilities.
| Class | Primary Purpose |
|---|---|
File | Represents a file or directory path |
Scanner | Reads text |
PrintWriter | Writes text, including formatted text |
FileInputStream | Reads bytes from a file |
FileOutputStream | Writes bytes to a file |
DataInputStream | Reads primitive values from a binary stream |
ObjectInputStream | Reads serialized Java objects |
ObjectOutputStream | Writes serialized Java objects |
Serializable | Marks a class whose objects may be serialized |
It is common for several of these classes to work together. A File identifies where the data is located, a file stream connects to that location, and another stream may add higher-level operations such as reading primitive values or writing complete objects.
Using try-with-resources ensures that these resources are automatically closed when the program is finished using them.