CPSC 170 Lab 12
More I/O

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, 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:

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

  2. 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. (No kidding; if you do, it seems to close 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 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.

  3. Write a program TestWrite that does the following: 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.

  4. Now write a program TestRead that does the following: 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.

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

  6. 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:

To see how object I/O works, do the following:

  1. Make the Student class implement the Serializable interface.

  2. 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!

  3. 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!

  4. 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 lab12 directory (just one per pair) and e-mail it to me with cpsc170 lab12 in the Subject line. Be sure to note in the e-mail and on the hardcopy both members of the pair.