Chapter 2
IN THIS CHAPTER
Knowing your Java streams
Reading and writing text streams
Reading and writing binary streams
I/O, I/O, it’s off to work I go.
Or so goes the classic song, which pretty much sums up the whole purpose of computers. Without I/O (input/output), computers — and the programs that run on them — would be worthless.
Imagining any useful computer program that doesn’t do some form of I/O is hard. Even the very first program presented in this book — the classic Hello, World!
program — does I/O; it displays a simple message onscreen.
In this chapter, you find out about Java’s most fundamental technique for getting data into and out of programs: streams. You’ve been working with streams all along in this book. When you use the System.out.print
or System.out.println
method to display text on the console, you’re actually sending data to an output stream, and when you use a Scanner
object to get data from System.in
, you’re reading data from an input stream.
In this chapter, you build on what you already know about stream I/O and see how it can be used to read and write data to hard-drive files.
A stream is simply a flow of characters to and from a program. The other end of the stream can be anything that can accept or generate a stream of characters, including a console window, a printer, a file on a disk drive, or even another program.
Streams have no idea of the structure or meaning of your data; a stream is just a sequence of characters. In later chapters of Book 8, you find out how to work with data at a higher level by using databases and XML.
You can roughly divide the world of Java stream I/O into two camps:
Character streams: Character streams read and write text characters that represent strings. You can connect a character stream to a text file to store text data on a hard drive. Typically, text files use special characters called delimiters to separate elements of the file. For example:
Usually you can display a text file in a text editor and make some sense of its contents.
To read a text file through a character stream, you usually work with the following classes:
File
: The File
class (which I cover in detail in Book 8, Chapter 1) represents a file on a hard drive. In file I/O applications, the main purpose of the File
class is to identify the file you want to read from or write to.FileReader
: The FileReader
class provides basic methods for reading data from a character stream that originates from a file. It provides methods that let you read data one character at a time. Usually, you don’t work with this class directly. Instead, you create a FileReader
object to connect your program to a file and then pass that object to the constructor of the BufferedReader
class, which provides more efficient access to the file. (This class extends the abstract class Reader
, which is the base class for a variety of classes that can read character data from a stream.)BufferedReader
: This class “wraps” around the FileReader
class to provide more efficient input. This class adds a buffer to the input stream that allows the input to be read from the hard drive in large chunks rather than a byte at a time, which can result in a huge improvement in performance. The BufferedReader
class lets you read data one character at a time or a line at a time. In most programs, you read data one line at a time and then use Java’s string-handling features to break the line into individual fields.Table 2-1 lists the most important constructors and methods of these classes.
TABLE 2-1 The BufferedReader and FileReader Classes
Constructor |
Description |
|
Creates a buffered reader from any object that extends the |
|
Creates a file reader from the specified |
|
Creates a file reader from the specified pathname. It throws |
Method |
Description |
|
Closes the file and throws |
|
Reads a single character from the file and returns it as an integer. The method returns –1 if the end of the file has been reached. It throws |
|
Reads an entire line and returns it as a string. The method returns |
|
Skips ahead the specified number of characters. |
In the following sections, you find out how to read a file named movies.txt
that contains one line each for ten of my favorite movies. Each line of the file contains the title of the movie, a tab, the year when the movie was released, another tab, and the price I paid for it. Here are the contents of the file:
It's a Wonderful Life→1946→14.95
Young Frankenstein→1974→16.95
Star Wars→1977→17.95
The Princess Bride→1987→16.95
Glory→1989→14.95
The Game→1997→14.95
Shakespeare in Love→1998→19.95
Zombieland→2009→18.95
The King's Speech→2010→17.85
Star Trek: Into Darkness→2013→19.95
(In this list, the arrows represent tab characters.) Later in this chapter, I show you a program that writes data to this file.
The normal way to connect a character stream to a file is to create a File
object for the file, using one of the techniques presented in Book 8, Chapter 1. Then you can call the FileReader
constructor to create a FileReader
object and pass this object to the BufferedReader
constructor to create a BufferedReader
object, as in this example:
File f = new File("movies.txt");
BufferedReader in = new BufferedReader(
new FileReader(f));
Here a BufferedReader
object is created to read the movies.txt
file.
To read a line from the file, you use the readLine
method of the BufferedReader
class. This method returns null
when the end of the file is reached. As a result, testing the string returned by the readLine
method in a while
loop to process all the lines in the file is common.
This code snippet reads each line from the file and prints it to the console:
String line = in.readLine();
while (line != null)
{
System.out.println(line);
line = in.readLine();
}
After you read a line of data from the file, you can use Java’s string-handling features to pull individual bits of data out of the line. In particular, you can use the split
method to separate the line into the individual strings that are separated by tabs. Then you can use the appropriate parse
methods (such as parseInt
and parseDouble
) to convert each string to its correct data type.
Here’s a routine that converts a line read from the movies.txt
file to the title (a string), year (an int
), and price (a double
):
String[] data = line.split(" ");
String title = data[0];
int year = Integer.parseInt(data[1]);
double price = Double.parseDouble(data[2]);
in.close();
Listing 2-1 shows a complete, albeit simple, program that reads the movies.txt
file and prints the contents of the file to the console.
LISTING 2-1 Reading from a Text File
import java.io.*; →1
import java.text.NumberFormat;
public class ReadFile
{
public static void main(String[] args)
{
NumberFormat cf = NumberFormat.getCurrencyInstance();
BufferedReader in = getReader("movies.txt"); →8
Movie movie = readMovie(in); →9
while (movie != null) →10
{
String msg = Integer.toString(movie.year);
msg += ": " + movie.title;
msg += " (" + cf.format(movie.price) + ")";
System.out.println(msg);
movie = readMovie(in);
}
} →18
private static BufferedReader getReader(String name) →19
{
BufferedReader in = null;
try
{
File file = new File(name);
in = new BufferedReader(
new FileReader(file) );
}
catch (FileNotFoundException e)
{
System.out.println(
"The file doesn't exist.");
System.exit(0);
}
return in;
}
private static Movie readMovie(BufferedReader in) →36
{
String title;
int year;
double price;
String line = "";
String[] data;
try
{
line = in.readLine();
}
catch (IOException e)
{
System.out.println("I/O Error");
System.exit(0);
}
if (line == null)
return null;
else
{
data = line.split(" ");
title = data[0];
year = Integer.parseInt(data[1]);
price = Double.parseDouble(data[2]);
return new Movie(title, year, price);
}
}
private static class Movie →63
{
public String title;
public int year;
public double price;
public Movie(String title, int year, double price)
{
this.title = title;
this.year = year;
this.price = price;
}
}
}
If you run this program, the following output is displayed on the console:
1946: It's a Wonderful Life ($14.95)
1974: Young Frankenstein ($16.95)
1977: Star Wars ($17.95)
1987: The Princess Bride ($16.95)
1989: Glory ($14.95)
1997: The Game ($14.95)
1998: Shakespeare in Love ($19.95)
2009: Zombieland ($18.95)
2010: The King's Speech ($17.85)
2013: Star Trek Into Darkness ($19.95)
Because I’ve already explained most of this code, the following paragraphs provide just a road map to this program:
import java.io.*
to import all the Java I/O classes used by the program.getReader
to create a BufferedReader
object that can read the file. The name of the file is passed to this method as a parameter. Note that in a real program, you’d probably get this filename from the user via a JFileChooser
dialog box or some other means. In any event, the BufferedReader
object returned by the getReader
method is saved in a variable named in
.readMovie
, is used to read each movie from the file. This method returns a Movie
object. (Movie
is a private class that’s defined later in the program.) If the end of the file has been reached, this method returns null
.while
loop is used to process each movie. This loop simply builds a message string from the Movie
object, displays it on the console, and then calls readMovie
to read the next movie in the file.getReader
method creates a BufferedReader
object for the filename passed as a parameter. If any exceptions are thrown during creation of the BufferedReader
object, the program exits.readMovie
method reads a line from the reader passed as a parameter, parses the data in the line, creates a Movie
object from the data, and returns the Movie
object. If the end of the file is reached, this method returns null
. The statement that reads the line from the file is enclosed in a try
/catch
block that exits the program if an I/O error occurs.Movie
class is a private inner class that defines the movie objects. To keep the class simple, it uses public fields and a single constructor that initializes the fields.The usual way to write data to a text file is to use the PrintWriter
class — which, as luck would have it, you’re already familiar with. It’s the same class that provides the print
and println
methods used to write console output. As a result, the only real trick to writing output to a text file is figuring out how to connect a print writer to a text file. To do that, you work with three classes:
FileWriter
: The FileWriter
class connects to a File
object but provides only rudimentary writing ability.BufferedWriter
: This class connects to a FileWriter
and provides output buffering. Without the buffer, data is written to the hard drive one character at a time. This class lets the program accumulate data in a buffer and writes the data only when the buffer is filled or when the program requests that the data be written.PrintWriter
: This class connects to a Writer
, which can be a BufferedWriter
, a FileWriter
, or any other object that extends the abstract Writer
class. Most often, you connect this class to a BufferedWriter
.The PrintWriter
class is the only one of these classes whose methods you usually use when you write data to a file. Table 2-2 lists the most important constructors and methods of this class.
TABLE 2-2 The PrintWriter, BufferedWriter, and FileWriter Classes
Constructor |
Description |
|
Creates a print writer for the specified output writer. |
|
Creates a print writer for the specified output writer. If the second parameter is |
|
Creates a buffered writer from the specified writer. Typically, you pass this constructor a |
|
Creates a file writer from the specified |
|
Creates a file writer from the specified |
|
Creates a file writer from the specified pathname and throws |
|
Creates a file writer from the specified pathname and throws |
PrintWriter Method |
Description |
|
Closes the file. |
|
Writes the contents of the buffer to the hard drive. |
|
Reads a single character from the file and returns it as an integer. The method returns –1 if the end of the file has been reached. It throws |
|
Writes the value, which can be any primitive type or any object. If the value is an object, the object’s |
|
Writes the value, which can be any primitive type or any object. If the value is an object, the object’s |
To connect a character stream to an output file, you first create a File
object for the file, as I describe in Book 8, Chapter 1. Then you call the PrintWriter
constructor to create a PrintWriter
object that you can use to write to the file. This constructor wraps around a BufferedWriter
object, which in turn wraps around a FileWriter
object like this:
File file = new File("movies.txt");
PrintWriter out =
new PrintWriter(
new BufferedWriter(
new FileWriter(file) ) );
If you find this arrangement a little confusing, that’s good! That makes me feel a little better, because I find it a little confusing too. The basic idea going on here is that each of the classes is adding a capability to the class it wraps. At the bottom is the FileWriter
class, which has the ability to write characters to a file. The BufferedWriter
class adds buffering to the mix, saving data in a buffer until it makes sense to write it all out to the file in one big spurt. The PrintWriter
class adds basic formatting capabilities, such as adding a line ending at the end of each line and converting primitive types to strings.
Both the FileWriter
and the PrintWriter
classes have an optional boolean
parameter you can use to add extra capabilities to the file stream. If you specify true
in the FileWriter
constructor, the file is appended if it exists. Appended simply means that any data in the file is retained; data that you write to the file in your program is simply added to the end of the file. Here’s a PrintWriter
constructor that appends data to its file:
File file = new File("movies.txt");
PrintWriter out =
new PrintWriter(
new BufferedWriter(
new FileWriter(file, true )))// append mode
If you specify false
instead of true
, or if you leave this parameter out, an existing file is deleted, and its data is lost.
The boolean
parameter in the PrintWriter
class has less dire consequences. It simply tells the PrintWriter
class that it should tell the BufferedWriter
class to flush its buffer whenever you use the println
method to write a line of data. Although this option may decrease the efficiency of your program by a small amount, it also makes the program a little more reliable, because it reduces the odds of losing data if your program or the whole computer crashes while unwritten data is in the buffer.
Unfortunately, the code for specifying this option looks a little goofy because of the way that the constructors for the BufferedWriter
and FileWriter
classes are nested:
File file = new File("movies.txt");
PrintWriter out =
new PrintWriter(
new BufferedWriter(
new FileWriter(file) ), true); ////mode flush
FileWriter fw = new FileWriter(file, true);
BufferedWriter bw = new BufferedWriter(fw);
PrintWriter out = new PrintWriter(bw, true);
If you find this coding technique easier to understand, by all means use it.
After you successfully connect a character stream to a file, writing data to it is as easy as writing text to the console. You just use the print
and println
methods exactly as though you were writing to the console.
One minor complication is that if you’re writing data to a text file in a delimited format, you have to include statements that write the delimiter characters to the file. Suppose (for example) that the title and year of a movie that you want to write to the text file are stored in String
variables named title
and year
. This snippet of code writes these fields with a tab delimiter between them:
System.out.print(title);
System.out.print(" ");
System.out.println(year);
Here the last item to be written is written with the println
method rather than the print
method. That ends the current line.
If you prefer to be a little more efficient, you can build a string representing the entire line and then write the line all at once, as follows:
String line = title + " " + year;
System.out.println(line);
This method is a little more efficient than the preceding version, but not as much as you’d think. In most cases, BufferedWriter
holds your text in a buffer until the println
method is called anyway.
out.flush();
Also, when you’re finished writing data to the file, you can close the file by calling the close
method, like this:
out.close();
Listing 2-2 shows a complete program that writes lines to a text file. The data written is taken from an array that’s hard-coded into the file, but you can easily imagine how to obtain the data from the user by prompting for console input or using text fields in a Swing application. (For full coverage of Swing, see Book 6.)
LISTING 2-2 Writing to a Text File
import java.io.*;
public class WriteFile
{
public static void main(String[] args) →4
{
Movie[] movies = getMovies();
PrintWriter out = openWriter("movies2.txt");
for (Movie m : movies)
writeMovie(m, out);
out.close();
}
private static Movie[] getMovies() →12
{
Movie[] movies = new Movie[10];
movies[0] = new Movie("It's a Wonderful Life",
1946, 14.95);
movies[1] = new Movie("Young Frankenstein",
1974, 16.95);
movies[2] = new Movie("Star Wars",
1977, 17.95);
movies[3] = new Movie("The Princess Bride",
1987, 16.95);
movies[4] = new Movie("Glory",
1989, 14.95);
movies[5] = new Movie("The Game",
1997, 14.95);
movies[6] = new Movie("Shakespeare in Love",
1998, 19.95);
movies[7] = new Movie("Zombieland",
1997, 18.95);
movies[8] = new Movie("The King's Speech",
1997, 19.95);
movies[9] = new Movie("Star Trek Into Darkness",
1997, 19.95);
return movies;
}
private static PrintWriter openWriter(String name) →37
{
try
{
File file = new File(name);
PrintWriter out =
new PrintWriter(
new BufferedWriter(
new FileWriter(file) ), true );
return out;
}
catch (IOException e)
{
System.out.println("I/O Error");
System.exit(0);
}
return null;
}
private static void writeMovie(Movie m, →55
PrintWriter out)
{
String line = m.title;
line += " " + Integer.toString(m.year);
line += " " + Double.toString(m.price);
out.println(line);
}
private static class Movie →63
{
public String title;
public int year;
public double price;
public Movie(String title, int year, double price)
{
this.title = title;
this.year = year;
this.price = price;
}
}
}
Because all the coding elements in this program have already been explained in this chapter, the following paragraphs just provide a road map to the major part of the program:
main
method begins by calling a method named getMovies
, which returns an array of Movie
objects to be written to the file. (The Movie
class is defined as an inner class later in the program.) Then it calls openWriter
, which creates a PrintWriter
object that the program can use to write data to the file. Next, it uses an enhanced for
loop to call the writeMovie
method for each movie in the array. This method accepts a Movie
object that contains the movie to be written and a PrintWriter
object to write the movie to. Finally, the PrintWriter
is closed.getMovies
method returns an array of Movie
objects that are written to a file. In a real-life program, you’d probably do something other than hard-code the movie information in this method. You might prompt the user to enter the data, for example, or use a Swing frame to get the data.openWriter
method creates a PrintWriter
object for the filename passed to it as a parameter. The PrintWriter
uses a buffer that’s flushed each time println
is called.writeMovie
method accepts as parameters a Movie
object to be written and the PrintWriter
to which the movie should be written. It creates a string that includes the title, a tab, the year, another tab, and the price; then it writes the string to the file.Movie
class is an inner class that defines a movie object. This class simply consists of three public fields (title, year, and price) and a constructor that initializes the fields.Binary streams, used to store data in raw binary format rather than in text format, are a bit tougher to read than character streams. The biggest obstacle to pass when you’re reading a binary stream is that you need to know exactly the type of each item that was written to the file. If any incorrect data is in the file, the program won’t work, so you need to ensure that the file contains the data your program expects it to contain.
To read a binary file, you usually work with the following classes:
File
: Once again, you use the File
class to represent the file itself.FileInputStream
: FileInputStream
is what connects the input stream to a file.BufferedInputStream
: This class adds buffering to the basic FileInputStream
, which improves the stream’s efficiency and gives it a moist and chewy texture.DataInputStream
: This class is the one you actually work with to read data from the stream. The other Stream
classes read a byte at a time. This class knows how to read basic data types, including primitive types and strings.Table 2-3 lists the vital constructors and methods of these classes.
TABLE 2-3 Classes for Reading Binary Streams
Constructors |
Description |
|
Creates a buffered input stream from any object that extends the |
|
Creates a data input stream from any object that extends the |
|
Creates a file input stream from the specified object. It throws |
|
Creates a file input stream from the specified pathname. It throws |
DataInputStream Methods |
Description |
|
Reads a |
|
Reads a |
|
Reads a |
|
Reads a |
|
Reads a |
|
Reads an |
|
Reads a |
|
Reads a |
|
Reads a string stored in UTF format from the input stream. It throws |
The following sections present programs that read and write data in a binary file named movies.dat
that contains information about movies. Each record in this file consists of a UTF (Unicode) string containing the movie’s title, an int
representing the year when the movie was released, and a double
representing the price I paid for the movie at my local discount video store. Although the format of this file is different from that of the movies.txt
file shown earlier in this chapter, the file contains the same data. Refer to the section “Reading Character Streams,” earlier in this chapter, to see a list of the movies in this file.
To read data from a binary file, you want to connect a DataInputStream
object to an input file. To do that, you use a File
object to represent the file, a FileInputStream
object that represents the file as an input stream, a BufferedInputStream
object that adds buffering to the mix, and finally a DataInputStream
object to provide the methods that read various data types. The constructor for such a beast looks like this:
File file = new File("movies.dat");
DataInputStream in = new DataInputStream(
new BufferedInputStream(
new FileInputStream(file) ) );
If all this nesting makes you nauseous, you can use this format instead:
File file = new File("movies.dat");
FileInputStream fs = new FileInputStream(file);
BufferedInputStream bs = new BufferedInputStream(fs);
DataInputStream in = new DataInputStream(bs);
Either way, the effect is the same.
With binary files, you don’t read an entire line into the program and parse it into individual fields. Instead, you use the various read
methods of the DataInputStream
class to read the fields one at a time. To do that, you have to know the exact sequence in which data values appear in the file.
Here’s a code snippet that reads the information for a single movie and stores the data in variables:
String title = in.readUTF();
int year = in.readInt();
double price = in.readDouble();
All of the read methods throw EOFException
if the end of the file is reached and IOException
if an I/O error occurs, so you need to call these methods inside a try
/catch
block that catches these exceptions. The readUTF
method also throws UTFDataFormatException
, but that exception is a type of IOException
, so you probably don’t need to catch it separately.
The read
methods usually are used in a while
loop to read all the data from the file. When the end of the file is reached, EOFException
is thrown. Then you can catch this exception and stop the loop. One way to do that is to use a boolean
variable to control the loop, as follows:
boolean eof = false;
while (!eof)
{
try
{
String title = in.readUTF();
int year = in.readInt();
double price = in.readDouble();
// do something with the data here
}
catch (EOFException e)
{
eof = true;
}
catch (IOException e)
{
System.out.println("An I/O error "
+ "has occurred!");
System.exit(0);
}
}
Here the boolean
variable eof
is set to true
when EOFException
is thrown, and the loop continues to execute as long as eof
is false
.
After you read a line of data from the file, you can use Java’s string-handling features to pull individual bits of data out of the line. In particular, you can use the split
method to separate the line into the individual strings that are separated by tabs. Then you can use the appropriate parse
methods to parse each string to its correct data type.
Here’s a routine that converts a line read from the movies.txt
file to the title (a string), year (an int
), and price (a double
):
String[] data = line.split(" ");
String title = data[0];
int year = Integer.parseInt(data[1]);
double price = Double.parseDouble(data[2]);
in.close();
This method also throws IOException
, so you want to place it inside a try
/catch
block.
Now that you’ve seen the individual elements of reading data from a binary file, Listing 2-3 presents a complete program that uses these techniques. This program reads the movies.dat
file; creates a Movie
object for each title, year, and price value; and prints a line on the console for each movie. If you run this program, the output looks exactly like the output from the text-file version presented in the section “Reading the movies.txt
file,” earlier in this chapter. (Note that you must first create the movies.dat
file before you can run this program. The program that creates the movies.dat file is found later in this chapter, in Listing 2-4.)
LISTING 2-3 Reading from a Binary File
import java.io.*;
import java.text.NumberFormat;
public class ReadBinaryFile
{
public static void main(String[] args) →5
{
NumberFormat cf = NumberFormat.getCurrencyInstance();
DataInputStream in = getStream("movies.dat");
boolean eof = false;
while (!eof)
{
Movie movie = readMovie(in);
if (movie == null)
eof = true;
else
{
String msg = Integer.toString(movie.year);
msg += ": " + movie.title;
msg += " (" + cf.format(movie.price) + ")";
System.out.println(msg);
}
}
closeFile(in);
}
private static DataInputStream getStream(String name) →25
{
DataInputStream in = null;
try
{
File file = new File(name);
in = new DataInputStream(
new BufferedInputStream(
new FileInputStream(file) ) );
}
catch (FileNotFoundException e)
{
System.out.println("The file doesn't exist.");
System.exit(0);
}
return in;
}
private static Movie readMovie(DataInputStream in) →42
{
String title = "";
int year = 0;;
double price = 0.0;;
try
{
title = in.readUTF();
year = in.readInt();
price = in.readDouble();
}
catch (EOFException e)
{
return null;
}
catch (IOException e)
{
System.out.println("I/O Error");
System.exit(0);
}
return new Movie(title, year, price);
}
private static void closeFile(DataInputStream in) →64
{
try
{
in.close();
}
catch(IOException e)
{
System.out.println("I/O Error closing file.");
System.out.println();
}
}
private static class Movie →76
{
public String title;
public int year;
public double price;
public Movie(String title, int year, double price)
{
this.title = title;
this.year = year;
this.price = price;
}
}
}
The following paragraphs describe what each method in this program does:
main
method is intentionally kept simple so that it can focus on controlling the flow of the program rather than doing the detail work of accessing the file. As a result, it calls a method named getStream
to get a data input stream object to read the file. Then it uses a while
loop to call a method named readMovie
to get a movie object. If the Movie
object isn’t null
, the movie’s data is printed to the console. Finally, when the loop ends, a method named closeFile
is called to close the file.getStream
method creates a DataInputStream
object for the filename passed as a parameter. If any exceptions are thrown, the program exits.readMovie
method reads the data for a single movie and creates a Movie
object. If the end of the file is reached, the method returns null
.closeFile
method closes the input stream.Movie
class is defined as a private inner class.To write data to a binary file, you use the following classes:
FileOutputStream
: The FileOutputStream
class connects to a File
object and creates an output stream that can write to the file. This output stream is limited in its capabilities, however, in that it can write only raw bytes to the file. In other words, it doesn’t know how to write values such as int
s, double
s, or strings.BufferedOutputStream
: This class connects to a FileOutputStream
and adds output buffering.DataOutputStream
: This class adds the ability to write primitive data types and strings to a stream.Table 2-4 lists the essential constructors and methods of these classes.
TABLE 2-4 The DataOutputStream, BufferedOutputStream, and FileOutputStream Classes
Constructor |
Description |
|
Creates a data output stream for the specified output stream. |
|
Creates a buffered output stream for the specified stream. Typically you pass this constructor a |
|
Creates a file writer from the file. It throws |
|
Creates a file writer from the file. It throws |
|
Creates a file writer from the specified pathname. It throws |
|
Creates a file writer from the specified pathname. It throws |
DataInputStream Method |
Description |
|
Closes the file. |
|
Writes the contents of the buffer to the hard drive. |
|
Returns the number of bytes written to the file. |
|
Writes a |
|
Writes a |
|
Writes a |
|
Writes a |
|
Writes a |
|
Writes an |
|
Writes a |
|
Writes a |
|
Writes a string stored in UTF format to the output stream. It throws |
Creating a DataOutputStream
object requires yet another one of those crazy nested constructor things:
File file = new File(name);
DataOutputStream out = new DataOutputStream(
new BufferedOutputStream(
new FileOutputStream(file) ) );
If you prefer, you can unravel the constructors like this:
File file = new File(name);
FileOutputStream fos = new FileOutputStream(file);
BufferedOutputStream bos = new BufferedOutputStream(fos);
DataOutputStream out = new DataOutputStream(bos);
The FileOutputStream
class has an optional boolean
parameter that you can use to indicate that the file should be appended if it exists. To use this feature, call the constructors like this:
File file = new File(name);
DataOutputStream out = new DataOutputStream(
new BufferedOutputStream(
new FileOutputStream(file, true) ) );
If you specify false
instead of true
or leave the parameter out, an existing file is deleted, and its data is lost.
After you successfully connect a DataOutputStream
to a file, writing data to it is simply a matter of calling the various write
methods to write different data types to the file. The following code writes the data for a Movie
object to the file:
out.writeUTF(movie.title);
out.writeInt(movie.year);
out.writeDouble(movie.price);
These methods throw IOException
, of course. As a result, you have to enclose them in a try
/catch
block.
out.flush();
Also, when you finish writing data to the file, close the file by calling the close
method, like this:
out.close();
The flush
and close
methods also throw IOException
, so you need a try
/catch
block to catch the exception.
Listing 2-4 presents a program that writes the movies.dat
file from an array of Movie
objects whose values are hard-coded into the program.
LISTING 2-4 Writing to a Binary File
import java.io.*;
public class WriteBinaryFile
{
public static void main(String[] args) →4
{
Movie[] movies = getMovies();
DataOutputStream out = openOutputStream("movies.dat");
for (Movie m : movies)
writeMovie(m, out);
closeFile(out);
}
private static Movie[] getMovies() →12
{
Movie[] movies = new Movie[10];
movies[0] = new Movie("It's a Wonderful Life",
1946, 14.95);
movies[1] = new Movie("Young Frankenstein",
1974, 16.95);
movies[2] = new Movie("Star Wars",
1977, 17.95);
movies[3] = new Movie("The Princess Bride",
1987, 16.95);
movies[4] = new Movie("Glory",
1989, 14.95);
movies[5] = new Movie("The Game",
1997, 14.95);
movies[6] = new Movie("Shakespeare in Love",
1998, 19.95);
movies[7] = new Movie("Zombieland",
1997, 18.95);
movies[8] = new Movie("The King's Speech",
1997, 19.95);
movies[9] = new Movie("Star Trek Into Darkness",
1997, 19.95);
return movies;
}
private static DataOutputStream
openOutputStream(String name) →38
{
DataOutputStream out = null;
try
{
File file = new File(name);
out = new DataOutputStream(
new BufferedOutputStream(
new FileOutputStream(file) ) );
return out;
}
catch (IOException e)
{
System.out.println(
"I/O Exception opening file.");
System.exit(0);
}
return out;
}
private static void writeMovie(Movie m, →57
DataOutputStream out)
{
try
{
out.writeUTF(m.title);
out.writeInt(m.year);
out.writeDouble(m.price);
}
catch (IOException e)
{
System.out.println(
"I/O Exception writing data.");
System.exit(0);
}
}
private static void closeFile(DataOutputStream out) →73
{
try
{
out.close();
}
catch (IOException e)
{
System.out.println("I/O Exception closing file.");
System.exit(0);
}
}
private static class Movie →85
{
public String title;
public int year;
public double price;
public Movie(String title, int year, double price)
{
this.title = title;
this.year = year;
this.price = price;
}
}
}
Because this chapter explains all the coding elements in this program, the following paragraphs just provide a road map to the major parts of the program:
main
method calls getMovies
to get an array of Movie
objects. Next, it calls openOutputStream
to get an output stream to write data to the file. Then an enhanced for
loop calls writeMovie
to write the movies to the file. Finally, the method calls closeFile
to close the file.getMovies
method creates an array of movies to be written to the file.openOutputStream
method creates a DataOutputStream
object so that the program can write data to the file.writeMovie
method accepts two parameters: the movie to be written and the output stream to write the data to.closeFile
method closes the file.Movie
class is included as an inner class.