Chapter 2

Working with File Streams

IN THIS CHAPTER

check Knowing your Java streams

check Reading and writing text streams

check 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.

Understanding Streams

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:

    • A comma-delimited file uses commas to separate individual fields of data.
    • A tab-delimited file uses tabs to separate fields.

    Usually you can display a text file in a text editor and make some sense of its contents.

  • Binary streams: Binary streams read and write individual bytes that represent primitive data types. You can connect a binary stream to a binary file to store binary data on a hard drive. The contents of binary files make perfect sense to the programs that read and write them, but if you try to open a binary file in a text editor, the file’s contents look like gibberish.

technicalstuff Conceptually, the trickiest part of understanding how streams work is getting your mind around all the different classes. Java has nearly 100 classes for working with streams. Fortunately, you need to know about only a few of them for most file I/O applications. In the rest of this chapter, I tell you about the most important classes for working with character and binary streams. (For a complete list of file I/O classes, browse to http://download.java.net/jdk9/docs/api and click java.io in the list of packages near the top left of the page.)

remember All the classes in this chapter are in the java.io package, so programs that work with file streams include an import java.io.* statement.

Reading Character Streams

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

BufferedReader(Reader in)

Creates a buffered reader from any object that extends the Reader class. Typically you pass this constructor a FileReader object.

FileReader(File file)

Creates a file reader from the specified File object. It throws FileNotFoundException if the file doesn’t exist or if it’s a directory rather than a file.

FileReader(String path)

Creates a file reader from the specified pathname. It throws FileNotFoundException if the file doesn’t exist or if it’s a directory rather than a file.

Method

Description

void close()

Closes the file and throws IOException.

int read()

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 IOException.

String readLine()

Reads an entire line and returns it as a string. The method returns null if the end of the file has been reached. It throws IOException.

void skip(long num)

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.

remember If you create this file with a text editor, make sure that your text editor preserves the tabs correctly.

Creating a BufferedReader

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.

Reading from a character stream

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]);

tip After the entire file is read, you can close the stream by calling the close method, like so:

in.close();

Reading the movies.txt file

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:

  1. →1 The program begins with import java.io.* to import all the Java I/O classes used by the program.
  2. →8 The program uses a method named 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.
  3. →9 Another method, named 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.
  4. →10 A 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.
  5. →18 The program ends without closing the file. That’s okay, though, because the file is closed automatically when the program that opened it ends. If the program were to go on with other processing after it finished with the file, you’d want to close the file first.
  6. →19 The 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.
  7. →36 The 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.
  8. →63 The 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.

Writing Character Streams

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

PrintWriter(Writer out)

Creates a print writer for the specified output writer.

PrintWriter(Writer out, boolean flush)

Creates a print writer for the specified output writer. If the second parameter is true, the buffer is automatically flushed whenever the println method is called.

BufferedWriter(Writer out)

Creates a buffered writer from the specified writer. Typically, you pass this constructor a FileWriter object.

FileWriter(File file)

Creates a file writer from the specified File object and throws IOException if an error occurs.

FileWriter(File file, boolean append)

Creates a file writer from the specified File object and throws IOException if an error occurs. If the second parameter is true, data is added to the end of the file if the file already exists.

FileWriter(String path)

Creates a file writer from the specified pathname and throws IOException if an error occurs.

FileWriter(String path, boolean append)

Creates a file writer from the specified pathname and throws IOException if an error occurs. If the second parameter is true, data is added to the end of the file if the file already exists.

PrintWriter Method

Description

void close()

Closes the file.

void flush()

Writes the contents of the buffer to the hard drive.

int read()

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 IOException.

void print(value)

Writes the value, which can be any primitive type or any object. If the value is an object, the object’s toString() method is called.

void println(value)

Writes the value, which can be any primitive type or any object. If the value is an object, the object’s toString() method is called. A line break is written following the value.

Connecting a PrintWriter to a text file

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

tip If all these nested constructors make your head spin, you can always construct each object separately and use variables to keep track of them. Here’s an example that does just that, and also turns on append mode for FileWriter and flush mode for PrintWriter:

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.

Writing to a character stream

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.

tip If you didn’t specify the flush option when you created the PrintWriter object, you can still periodically force any data in the buffer to be written to the hard drive by calling the flush method, as follows:

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();

Writing the movies.txt file

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:

  1. →4 The 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.
  2. →12 The 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.
  3. →37 The 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.
  4. →55 The 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.
  5. →63 The 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.

Reading Binary Streams

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

BufferedInputStream(InputStream in)

Creates a buffered input stream from any object that extends the InputStream class. Typically you pass this constructor a FileInputStream object.

DataInputStream(InputStream in)

Creates a data input stream from any object that extends the InputStream class. Typically you pass this constructor a BufferedInputStream object.

FileInputStream File (File file)

Creates a file input stream from the specified object. It throws FileNotFoundException if the file doesn’t exist or if it’s a directory rather than a file.

FileInputStream(String path)

Creates a file input stream from the specified pathname. It throws FileNotFoundException if the file doesn’t exist or if it’s a directory rather than a file.

DataInputStream Methods

Description

boolean readBoolean()

Reads a boolean value from the input stream. It throws EOFException and IOException.

byte readByte()

Reads a byte value from the input stream. It throws EOFException and IOException.

char readChar()

Reads a char value from the input stream. It throws EOFException and IOException.

double readDouble()

Reads a double value from the input stream. It throws EOFException and IOException.

float readFloat()

Reads a float value from the input stream. It throws EOFException and IOException.

int readInt()

Reads an int value from the input stream. It throws EOFException and IOException.

long readLong()

Reads a long value from the input stream. It throws EOFException and IOException.

short readShort()

Reads a short value from the input stream. It throws EOFException and IOException.

String readUTF()

Reads a string stored in UTF format from the input stream. It throws EOFException, IOException, and UTFDataFormatException.

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.

technicalstuff If you’re unfamiliar with UTF, it is a standardized way of encoding characters in text files. The most commonly used forms of UTF are UTF-8, which encodes each character as a single byte, and UTF-16, which encodes each character as a 2-byte sequence. For more information, use your favorite search engine to search for UTF.

Creating a DataInputStream

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.

Reading from a data input stream

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]);

tip After the entire file has been read, you can close the stream by calling the close method, like this:

in.close();

This method also throws IOException, so you want to place it inside a try/catch block.

Reading the movies.dat file

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:

  1. →5 The 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.
  2. →25 The getStream method creates a DataInputStream object for the filename passed as a parameter. If any exceptions are thrown, the program exits.
  3. →42 The 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.
  4. →64 The closeFile method closes the input stream.
  5. →76 As in the other programs in this chapter, the Movie class is defined as a private inner class.

Writing Binary Streams

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 ints, doubles, 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

DataOutputStream(OutputStream out)

Creates a data output stream for the specified output stream.

BufferedIOutputStream(OutputStream out)

Creates a buffered output stream for the specified stream. Typically you pass this constructor a FileOutputStream object.

FileOutputStream(File file)

Creates a file writer from the file. It throws FileNotFoundException if an error occurs.

FileOutputStream(File file, boolean append)

Creates a file writer from the file. It throws FileNotFoundException if an error occurs. If the second parameter is true, data is added to the end of the file if the file already exists.

FileOutputStream(String path)

Creates a file writer from the specified pathname. It throws FileNotFoundException if an error occurs.

FileOutputStream(String path, boolean append)

Creates a file writer from the specified pathname. It throws FileNotFoundException if an error occurs. If the second parameter is true, data is added to the end of the file if the file already exists.

DataInputStream Method

Description

void close()

Closes the file.

void flush()

Writes the contents of the buffer to the hard drive.

int size()

Returns the number of bytes written to the file.

void writeBoolean(boolean value)

Writes a boolean value to the output stream. It throws IOException.

void writeByte(byte value)

Writes a byte value to the output stream. It throws IOException.

void writeChar(char value)

Writes a char value to the output stream. It throws IOException.

void writeDouble(double value)

Writes a double value to the output stream. It throws IOException.

void writeFloat(float value)

Writes a float value to the output stream. It throws IOException.

void writeInt(int value)

Writes an int value to the output stream. It throws IOException.

void writeLong(long value)

Writes a long value to the output stream. It throws IOException.

void writeShort(short value)

Writes a short value to the output stream. It throws IOException.

void writeUTF(String value)

Writes a string stored in UTF format to the output stream. It throws EOFException, IOException, and UTFDataFormatException.

Creating a DataOutputStream

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.

Writing to a binary stream

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.

tip If you included the BufferedOutputStream class in the stream, it accumulates data in its buffer until it decides to write the data to the hard drive. If you want, you can force the buffer to be written to the hard drive by calling the flush method, like this:

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.

Writing the movies.dat file

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:

  1. →4 The 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.
  2. →12 The getMovies method creates an array of movies to be written to the file.
  3. →38 The openOutputStream method creates a DataOutputStream object so that the program can write data to the file.
  4. →57 The writeMovie method accepts two parameters: the movie to be written and the output stream to write the data to.
  5. →73 The closeFile method closes the file.
  6. →85 Once again, the Movie class is included as an inner class.
..................Content has been hidden....................

You can't read the all page of ebook, please click here login for view all page.
Reset