CPSC 170 Lab 12More I/O
IMPORTANT: This lab is to be done using pair programming,
which means that ALL code is produced with two (or three) people
sitting at one computer. Take turns at the keyboard; each person in the
pair/trio must spend a significant amount of time there.
Put the names in your pair/trio at the
top of your programs and submit only once for each pair/trio.
As usual, create a lab12 directory for today's lab, open this
document in Mozilla, 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
by using a loop to store and then read each value. Of course, in this
case you need to be able to tell when to stop
reading, e.g. by reaching EOF.
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 represents 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. (You'll need
to import java.io.*).
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.
This time you will have to read directly from
your Reader -- for example, you can't construct a BufferedReader to use
inside this method. (If you do, it closes the entire
reader when the method returns, so you don't get any data after the first
read.) 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 if you had used the nextLine method of a Scanner or
the readLine method of a BufferedReader.
If the read method can't read an entire
object (e.g., if EOF is encountered or it gets 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.
Note that you only need one Student variable; just use a loop to create a
student and then write it to each file -- no need to store it after it has
been written.
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 each 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 will need to
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. Since
ObjectOutputStream is a processing stream, it needs to be connected
to a data stream (something that actually hooks up to the destination).
To write to a file you would typically create an ObjectOutputStream from
a FileOutputStream.
- 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. Like ObjectOutputStream,
ObjectInputStream is a processing stream and so needs to be connected
to a data stream that actually hooks up to the source.
To read from a file you
would typically create an ObjectInputStream from 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 from file1, 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
file1. (Still write the toString to file2, which should still be a
FileWriter.) 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) to
read from file1.
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.