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.
Things get more complex if you are mixing data types (e.g. strings and
integers or doubles and integers). This frequently occurs when you
want to write an object to a file.
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 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. As we discussed previously, a PrintWriter has more
useful methods, so you
may want to construct a PrintWriter from the Writer that is passed in.
Make sure that you pay careful attention to exactly what is getting
written to
the file. It can be any arrangement you want, but keep in mind that you will
eventually want another program to be able to read your student information in
again.
- Add a static method public static Student read(Scanner in)
that takes a scanner, reads information for a student from that scanner,
and returns a Student object containing that information. Note that this is
a static method (Why?) 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. Note: Your method
should be able to seamlessly handle the following names "Jane", "Jane Doe" and
"Jane Doe 2 " (the last is generated by the default constructor). Think
carefully about how to make your Scanner handle this (and maybe read about
scanners in the
Java documentation)
- 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.
- Directly writes the result of the toString method for each student
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
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 the end of file (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 should incorporate
this into the flow of your program.
- As before, compare the output files. They should be identical.
HAND IN:
- Turn in hardcopies of your code
- Tar your directory and email to me with cpsc170 lab12
in
the Subject line.
Make sure that the names of all members of your group are on all
submissions