Programming (Java) Streams, Readers and Writers Streams, Readers and Writers C. Horstmann, Big Java, Ch 15 is an excellent chapter about Streams. These notes pick out a few essentials to show "how to do..." common tasks in reading and writing files. The jave.io.* package defines a framework of I/O classes. The roots of the hierarchies are InputStream, and OutputStream for byte-by-byte reading and writing, Reader and Writer for reading and writing characters. Some Classes in java.io InputStream, OutputStream FileInputStream, FileOutputStream ObjectInputStream, ObjectOutputStream Reader, Writer FileReader, FileWriter BufferedReader PrintWriter Comment read/write a byte read/write bytes from/to a file read/write serialized objects read/write characters read/write character from/to a text file reads a line of text from a file into a String print() methods for all types to output as text The basic methods of Streams are to read and write one byte of data, and for Readers and Writers to read and write one character of text. Sub-classes provide utility methods that read and write more convenient structures, e.g. Arrays of bytes, Strings, and Objects. Associating a file with an I/O stream FileInputStream and FileOutputStream have constructors that take a filename as a parameter. This sets up reading and writing to a named file. Similarly FileReader and FileWriter may be used to associate text streams with named files. FileInputStream infile = new FileInputStream("somefile.dat"); FileOutputStream outfile = new FileOutputStream("datafile.dat"); FileWriter out = new FileWriter("out.txt"); The java.io classes provide high level I/O methods by using the 'decorator' pattern. Classes add functionality to other classes by 'wrapping' them. For example a BufferedReader can take a FileReader as a parameter. This enables text files to be read efficient line by line from a file using the readLine() method of BufferedReader. BufferedReader in = new BufferedReader(new FileReader("mytext.txt")); String str = in.readLine(); //read first line into a String while (str!=null) { //null signals end of file ...process str... //process the current string str = in.readLine(); //get the next line } in.close(); //close the file This is the classic algorithm structure for file processing. Note that in Java we have to try to read ahead. If the read fails, then this signals that the end of file has been reached. Similarly text can be sent to an output file by wrapping a FileWriter with a PrintWriter. PrintWriter out = new PrintWriter(new FileWriter("report.txt")); dmu/lz/oct03 1 Programming (Java) Streams, Readers and Writers The PrintWriter class has print() and println() methods for every primitive type and for Objects. These methods format the primitive type into characters, and in the case of Object it outputs the text returned by the object's toString() method. Example: to output each item in an ArrayList, list, on a separate line. We make use of the ArrayList's iterator method to access each item sequentially. PrintWriter out = new PrintWriter(new FileWriter("report.txt")); Iterator iter = list.iterator(); while (iter.hasNext()) { out.println(iter.next()); } out.flush(); //ensure output buffers are emptied out.close(); //close the file when finished Operating systems tend to buffer output into larger sized blocks before writing to disk. The flush() command forces the buffers to be written. Always close() the file when finished. Reading and Writing Objects Java provides two very powerful classes to read and write objects. The ObjectOutputStream and the ObjectInputStream, with methods readObject() and writeObject() respectively. public final Object readObject() throws ClassNotFoundException, IOException public final void writeObject() throws IOException Only objects that implement the java.io.Serializable interface can be written or read using these methods. The Serializable interface has no methods; it only acts a flag to indicate that a class may be serialized. The usual way to save data for an application is to wrap it up in one object and then save that object. Example: the code to save an object, myObject, to a file called "data.obj" is as follows: ObjectOutputStream out = new ObjectOutputStream( new FileOutputStream("data.obj") ); out.writeObject(myObject); out.close(); Reading the object back is also one line of code. The readObject() method returns an Object. This has to be cast to the object's type. ObjectInputStream in = new ObjectInputStream( new FileInputStream("data.obj") ); ObjectType obj = (ObjectType) in.readObject(); in.close(); Exceptions I/O methods are always prone to failure, e.g. file does not exist, or a file is closed, or disk fault. Therefore most code that does any I/O is usually wrapped in a try..catch statement. Alternatively, the method header carries the appropriate throws clause to delegate the error higher up the method call stack. The most common exceptions are IOException, and ClassNotFoundException when using the readObject() method. dmu/lz/oct03 2 Programming (Java) Streams, Readers and Writers The class below wraps the object i/o code into two utility methods. /* CSCI2014 * An Object file save and load utility. * Provides convenience class methods to: * - save an object to a named file * - load an object from a named file * The Object must be Serializable. * * @author lz * @version oct 03 */ import java.io.*; class ObjectFileUtility { /* Writes an object to the named file. */ public static void saveObject(Serializable obj, String filename) throws IOException { ObjectOutputStream oos = new ObjectOutputStream( new FileOutputStream(filename) ); oos.writeObject(obj); oos.flush(); oos.close(); } /* Returns the object saved in filename. * Hides some of the exception handling by re-throwing any * ClassNotFoundException as an IOException. */ public static Object loadObject(String filename) throws IOException { ObjectInputStream ois = new ObjectInputStream( new FileInputStream(filename) ); try { Object obj = ois.readObject(); ois.close(); return obj; } catch (ClassNotFoundException e) { throw new IOException("ClassNotFoundException"); } } } The example programs below demonstrate how this utility may be used to save and load an AppointmentDiary object (refer to Exercises 1 and 2). Note that the class headers for both Appointment and AppointmentDiary need to include the clause implements java.io.Serializable. dmu/lz/oct03 3 Programming (Java) Streams, Readers and Writers class Appointment implements java.io.Serializable { private String name; //name of person to meet private int time; //appointment time ... as before ... } import java.util.*; class AppointmentDiary implements java.io.Serializable { private String owner; private ArrayList diary; //the diary owner's id //the appointment collection object //Constructor public AppointmentDiary(String owner) { this.owner = owner; diary =new ArrayList(); } public void addAppointment(Appointment apt) { diary.add(apt); } public void cancelAppointmentAt(int time) { Appointment apt = this.getAppointmentAt(time); diary.remove(apt); } public Appointment getAppointmentAt(int time) { Appointment apt = null; boolean found = false; Iterator iter = diary.iterator(); while (iter.hasNext()&& !found) { apt = (Appointment) iter.next(); if (apt.getTime() == time) found = true; } return found ? apt : null; //null if not found } public String toString() { return "Diary for " + owner + ":\n " + diary; } } dmu/lz/oct03 4 Programming (Java) Streams, Readers and Writers This program creates an AppointmentDairy, populates it with a few appointment objects, and then saves all the data to file. When the AppointmentDiary is saved, all of its attributes are serialized to file: this includes the collection of appointment objects. /* CSCI2014 * Author: lz, oct 2003 * Program to test saving an AppointmentDiary to a file */ class DiarySaveTest { public static void main(String[] arg) { AppointmentDiary diary = new AppointmentDiary("The Boss"); diary.addAppointment(new Appointment("fred", 1130)); diary.addAppointment(new Appointment("ali", 1320)); diary.addAppointment(new Appointment("jim", 1030)); diary.addAppointment(new Appointment("mary", 1215)); diary.addAppointment(new Appointment("rio", 900)); diary.addAppointment(new Appointment("mary", 1500)); System.out.println(diary); try { ObjectFileUtility.saveObject(diary, "AppointmentDiary.object"); System.out.println("AppointmentDiary saved."); } catch (java.io.IOException e) { System.out.println("Save failed. " + e.getMessage()); } } } The following program can be run after the DiarySaveTest program. It demonstrates how the previously saved AppointmentDiary may be reloaded into memory again. The I/O section is written within a try..catch statement in order to trap the declared exceptions. /* CSCI2014 * Author: lz, oct 2003 * Program to load a previously saved AppointmentDiary object. */ class DiaryLoadTest { public static void main(String[] arg) { AppointmentDiary aDiary = null; try { Object obj; obj = ObjectFileUtility.loadObject("AppointmentDiary.object"); aDiary = (AppointmentDiary) obj; } catch (java.io.IOException e) { System.out.println("Load failed. " + e.getMessage()); } System.out.println(aDiary); } } dmu/lz/oct03 5