Most Java I/O is defined using streams (random access I/O is also provided via the class java.io.RandomAccessFile, but that will not be discussed here). An input stream brings in information to your program from some source, and an output stream sends information from your program to some destination. The source of an input stream can be the standard input, a file, a pipe, or the program's memory. Similarly, the destination of an output stream can be the standard output, the standard error, a file, a pipe, or memory. (Only the use of standard input/output/error and files are discussed here. See a Java reference book for a more in-depth discussion of Java I/O.)
The package java.io
provides two class hierarchies that support streams; one for byte streams and the other for character streams. (Don't forget to include
Which particular class you use for I/O will depend on where you want
to read from or write to (e.g., the standard input or output vs. a file),
and whether you want to read or write "human-readable" data (i.e.,
ascii characters) or binary data.
As we have seen before, the simplest way to produce human-readable
(ascii) output is to use System.out.print and System.out.println to
write to standard output (these methods are overloaded to handle the
output of all primitive types, as well as the output of non-primitive
types using their toString methods).
Similarly, you can use System.err.print and System.err.println to write to
standard error.
To write human-readable output to a file, you should use a
PrintWriter.
The PrintWriter class includes the following methods:
Note that although standard output and standard error (System.out and
System.err) provide the same methods, they are not PrintWriters;
they are
PrintStreams, which are byte streams rather than character streams.
You can convert System.out to a PrintWriter by using System.out as the
argument to the PrintWriter constructor (and similarly for System.err):
Not every PrintWriter writes to a file;
for example, if you convert System.out to a PrintWriter as described
above, it will still write to the standard output.
To create a PrintWriter that does write to a file, you must first create
a
FileWriter.
The FileWriter class has a constructor that takes a String as its argument
(the name of the file), and another constructor that takes a File
as its argument.
So to create a FileWriter to write to the file output.text, you
could use either of the following code fragments:
Ascii Output
new PrintWriter(System.out)
You would need to do this, for example, if you wanted to pass System.out
as an argument to a method whose formal parameter was of type PrintWriter.
// fragment 1
FileWriter fw = new FileWriter("output.text");
// fragment 2
File outFile = new File("output.text");
FileWriter fw = new FileWriter(outFile);
The advantage of first creating a File, is that you can test its properties
before trying to write to it.
The
File class includes the following methods:
Efficiency must also be considered when writing to a file. If you create a PrintWriter from a FileWriter, the file will be accessed every time you call the print or println methods. It is more efficient to use buffering, which can be done by creating a BufferedWriter from your FileWriter, and using the BufferedWriter as the argument to the PrintWriter's constructor. For example, here's how you might create a PrintWriter to write to the file output.text:
File outFile; PrintWriter pw; outFile = new File("output.text"); if (! outFile.exists() || (outFile.isFile() && outFile.canWrite())) { pw = new PrintWriter(new BufferedWriter(new FileWriter(outFile))); } else { System.err.println("ERROR: Cannot write to file output.text." ); }
Two reasonable ways to read ascii values are:
File inFile; BufferedReader br; inFile = new File("input.text"); if (!inFile.exists() || !inFile.canRead()) { System.err.println("ERROR: cannot read file input.text"); } else { int ch, total=0; br = new BufferedReader(new FileReader(inFile)); while ((ch = br.read()) != -1) total++; System.out.println(total + " chars"); br.close(); }
Write a program that copies the first file named on the command line to the second file named on the command line, removing all comments. A comment starts with two slashes (//) and continues to the end of the current line. For example, if the input file is:
Once upon a time // a typical beginning... there was a girl who liked division like 1/2. // handle the single slash! // This comment must go, so there will be a blank line in the output The end.then the output file should be:
Once upon a time there was a girl who liked division like 1/2. The end.
The StreamTokenizer class provides the method nextToken, which skips whitespace, and returns an integer value indicating the type of the next token in the input. It also sets the StreamTokenizer field ttype to the same value. For tokens that are words, the nextToken method sets the sval field of the StreamTokenizer to the actual value of the token, and for tokens that are numbers, the nextToken method sets the nval field of the StreamTokenizer to the actual value of the token. There are four predefined values for token types:
The StreamTokenizer class also provides a pushBack method, which causes the next call to nextToken to return the same token again, and a lineno method, which returns the current line number.
To create a StreamTokenizer to read from standard input, call the StreamTokenizer constructor function with System.in as its argument. To create a StreamTokenizer to read from a file, call the StreamTokenizer constructor function with a BufferedReader, created using a FileReader associated with the file. (As for FileWriter, you can create a FileReader using a String -- the file name -- or using a File object.) For example, the following code uses a StreamTokenizer to read the file grades.text, which contains a list of student names, each followed by a list of grades. It computes the average for each student, and writes the students' names and their average grades to the file averages.text:
BufferedReader br = new BufferedReader(new FileReader("grades.text")); StreamTokenizer input = new StreamTokenizer(br); PrintWriter output = new PrintWriter(new BufferedWriter(new FileWriter( "averages.text"))); // read info for each student until end-of-file while (input.nextToken() != StreamTokenizer.TT_EOF) { if (input.ttype != StreamTokenizer.TT_WORD) { // bad data in input -- give error msg and quit System.err.println("Bad input on line " + input.lineno()); System.err.println("Student name expected."); System.exit(1); } String name = input.sval; int grade, total = 0; int numGrades = 0; // read all grades for current student while (input.nextToken() == StreamTokenizer.TT_NUMBER) { grade = (int)input.nval; total += grade; numGrades++; } // put back the current token (which should be EOF or WORD) // to be read again in the main loop input.pushBack(); // write student name and average output.println(name + "\t" + (double)total/numGrades); } // close input and output files br.close(); output.close();StreamTokenizer methods are provided to define which characters make up words (the default is upper- and lower-case letters and digits), which characters are whitespace (the default is spaces, tabs, and newlines), which characters delimit quoted strings, and which characters start single-line comments (such comments are treated as whitespace).
Write a program that reads a file of sums and products (one per line) and writes the values of the expressions to another file. The two file names are to be supplied on the command line. For example, if the input file contains:
23 + 3 .5 * 24 12 + 1.6then the output file should contain:
26.0 12.0 13.6
Shown below is part of the inheritance hierarchy related to Java I/O (all of these classes are defined in the package java.io). The classes that implement character streams are shown in red; those that implement byte streams are shown in blue.
The following tables summarize how to construct one class from another, by giving the types of the arguments to the classes's constructor functions. (Note: these tables only provide partial information; in many cases there are other constructors as well.)
CLASS | CONSTRUCTOR ARG TYPES |
BufferedWriter | Writer |
FileWriter | File or String |
PrintWriter | OutputStream or Writer |
BufferedReader | Reader |
FileReader | File or String |
CLASS | CONSTRUCTOR ARG TYPES |
File | String |
StreamTokenizer | InputStream or Reader |
Solutions to Self-Study Questions
Test Yourself #1
Question 1.
// write the numbers from 1 to 10, one per line, to the file named on the
// command line if that file does not already exist; otherwise, give an
// error message.
import java.io.*;
class OneToTen {
public static void main(String[] args) throws java.io.IOException {
File outFile;
PrintWriter pw;
outFile = new File(args[0]);
if (! outFile.exists()) {
pw = new PrintWriter(new BufferedWriter(new FileWriter(outFile)));
for (int k = 1; k<=10; k++) {
pw.println(k);
}
pw.flush();
pw.close();
}
else {
System.err.println("ERROR: file " + args[0] + " already exists");
}
}
}
Question 2.
// count the number of "normal" (non-directory) files in the directory named
// on the command line and in all subdirectories; write the total to standard
// output
import java.io.*;
class CountFiles {
// return the number of "normal" (non-directory) files in the given
// directory and in all subdirectories
static int Count(File dir) {
if (!dir.isDirectory()) {
System.err.println("File " + dir.getName() + " is not a directory.");
System.exit(1);
}
int total = 0;
String [] files = dir.list();
String thisDir = dir.getPath();
// count non-directory files in this directory and recurse for each
// that IS a directory
for (int k=0; k<files.length; k++) {
File f = new File(thisDir + "/" + files[k]);
if (!f.isDirectory()) total++;
else total += Count(f);
}
return total;
}
public static void main(String[] args) {
if (args.length < 1) {
System.err.println("Please supply a directory name.");
System.exit(1);
}
File dir = new File(args[0]);
if (!dir.isDirectory()) {
System.err.println("File " + args[0] + " is not a directory.");
}
System.out.println("total number of normal files: " + Count(dir));
}
}