CPSC 170 Lab 11More I/O
As usual, create a lab11 directory for today's lab, open this
document in Netscape, and start emacs.
Writing Objects as Text
Input and output of objects is a tricky business. If your program
manipulates an integer or other primitive type
and you want to store that value
to a file, you can write it as text. When you want to
retrieve it you can read it back as text and use parseInt (or similar)
to get
the numeric value. An arbitrary number
of integers can be stored the same way,
but using a loop to store and then read each value. Of course, in this
case you need to be able to tell, e.g. by reaching EOF, when to stop
reading.
But if you want to write an object to a
file, it's not so easy.
You have to write each piece of information in the object
in such a way that it is reasonably easy to retrieve it later, and
then remember how you stored it so that you can read it back correctly.
Thus a class might have read and write methods that
take text streams (a Reader and Writer respectively) and do exactly this.
Note that a class's toString method is usually intended for display, and
often contains extraneous information (e.g., labeling) that makes it
inappropriate for general data storage.
File Student.java contains a class
that stores information about a student. Save this file to your
directory and open it in emacs.
Note that it has a
toString method that returns the student information, neatly formatted
and labeled. Proceed as follows:
- Add a method public void write(Writer out) to the
Student class that takes a
Writer and writes the student information to that Writer.
Consult your text or the Java documentation
to see the methods of the Writer class -- they aren't as helpful as
you might like. PrintWriter has more useful methods, so you
may want to construct a PrintWriter from the Writer that is passed in.
- Add a static method public static Student read(Reader in)
that takes a
Reader, reads information for a student from that Reader, and returns
a Student object containing that information. Note that this is
a static method.
To maintain generality, you will have to read directly from
your Reader -- for example, you can't construct a BufferedReader to use
inside this method. This means you'll have to read the input one
character at a time using the read() method. (Remember that read()
actually returns an int so that -1 can flag EOF, so if you really want
chars you'll need to cast them.) It's not as hard as it
sounds; just read characters and stick them into a string until
you hit end of line ('\n'), and then do whatever you would have
done using a BufferedReader.
If the read method can't read an entire
object (e.g., if EOF is encountered or it get a NumberFormatException),
it should return null -- it should not throw an exception.
- Write a program TestWrite that does the following:
- Prompts for and reads in two filenames, file1 and file2.
- Creates 5 "random" students (the default constructor is useful for this).
- Uses the write method for each student to write its information to
file1.
- Uses the toString method for each student to write its information to
file2.
You don't need to store all 5 students; just use a loop to create a
student and then write it to each file.
Don't forget to close the files when you're done writing.
Run your
program and inspect the files to see if they seem reasonable.
- Now write a program TestRead that does the following:
- Prompts for and reads in two filenames, file1 and file2.
- Uses the read method of the Student class to read 5 students
from file1.
- Uses the toString method for student object to write it to file2.
Again, you should do the read and the write in a single loop.
Don't forget to close file2. Now run TestRead, passing
in the first output file from TestWrite as file1, and a new file as file2.
- Visually compare file2 from TestWrite and file2 from TestRead (they will
need to have different names); they should look the same.
Then use the unix utility diff to see if they really are identical
(which they should be): diff file1 file2
will list any differences
between the files. If any differences appear, figure out why and
correct your programs as necessary.
- Now modify TestWrite so that it generates a random number between 1 and 10
(inclusive), and generates and writes that number of students. Modify
TestRead so that it reads all of the objects in the file
instead of reading a fixed
number of objects. (Don't rely on the upper bound of 10; TestRead should
work for any number.) Test your programs.
Writing Objects Directly
Java supports object I/O, that is,
streams and methods that allow objects to be written and read directly.
You can read about this in section 8.4 of the text and
consult the Java documentation,
but here's an overview:
- Object I/O can only be used for objects that are serializable, that
is, whose classes implement the Serializable interface. Fortunately,
this interface
has no methods, so it's no trouble to implement!
- Objects must be written to an ObjectOutputStream. Note that this is
a stream, not a writer; this is your clue that the objects are written
in binary form, not as text. ObjectOutputStream has a writeObject
method that takes an object and writes it to the stream. To write
to a file you would typically hook up an ObjectOutputStream to
a FileOutputStream as in the example in listing 8.10.
- Objects must be read from an ObjectInputStream. Note that this is
a stream, not a reader; again, this means that it is not expecting text.
ObjectInputStream has a readObject
method that returns the next object read. To read from a file you
would typically hook up an ObjectInputStream to a FileInputStream.
To see how object I/O works, do the following:
- Make the Student class implement the Serializable interface.
- Copy TestWrite.java into TestWrite2.java.
Modify it so that instead of creating a FileWriter, it creates
a FileOutputStream (used for binary files) and constructs an ObjectOutputStream
from it. Then use the writeObject method of the
ObjectOutputStream class to write the Student objects directly to
the file. Note that you will NOT use the write method of the
Student class!
- Copy TestRead.java into TestRead2.java, and similarly modify
it to use an ObjectInputStream (created from a FileInputStream).
Then use the readObject method of that class to read the objects
from the file until EOF is encountered -- again, you will NOT use the
read method of the Student class.
IMPORTANT: Attempting to read from an ObjectInputStream that is at
EOF does not return null -- it throws an EOFException. You will
need to deal with this!
- As before, compare the output files. They should be identical.
What to turn in (one per pair)
Turn in hardcopy of TestWrite.java, TestWrite2.java, TestRead.java,
TestRead2.java, and Student.java. Tar your lab11 directory (just one
per pair) and e-mail
it to me with cpsc170 lab11 in the Subject line. Be sure to note
in the e-mail and on the hardcopy both members of the pair.