java.io

In Java, you read a sequence of bytes from an InputStream and write a sequence of bytes to an OutputStream. But - Java characters are implemented with the two­byte­per­character Unicode system, and that can make byte­oriented streams inconvenient. So two other hierarchies are defined for processing character streams: Reader and Writer. For nearly every input stream there is a corresponding output stream, and for most input or output streams there is a corresponding reader or writer character stream of similar functionality.

The source or destination of streams can be files, or network connections, or other streams. This generality means that information coming from local files, or remote machines, or concurrent threads, or other object layers can all be handled the same way. "Java goes overboard with streams and grants you the choice (or burdens you with the choice) of: buffering lookahead, random access, text formatting, or binary data." [Horstmann, vol2, p6]

The java.io package defines several types of streams. The stream types usually have input/output pairs, and most of them have both byte stream and character stream variants. [Source: Gosling, p236]

The I/O package also has input and output streams that have no output or input counterpart. [Source: Gosling, p237] In addition to these stream types, a few other useful I/0 classes are provided. [Source: Gosling, p237] The classes/interfaces to be discussed in this section are listed below.
   java.lang.Object
      java.io.File
      java.io.RandomAccessFile
      java.io.InputStream
      java.io.OutputStream
      java.io.Reader
      java.io.Writer
      java.io.Serializable

The File class

File represents the name of a file or directory that may (or may not) exist in the local file system. Creating a File object does not look for the specified name, or open the file, or create a new file; it simply creates an encapsulation of the specified file name. You cannot read from or write to a File object. The class exists primarily to name files, query file attributes, and manipulate directories in a system­independent fashion.

The example below recursively lists the directory specified on the command line to standard out. A File object is created on line 5. The contents of the directory are retrieved with one call to list() (line 6). For each entry returned, we want to identify which are directories. Line 8 uses an alternate constructor to create a File object, and line 9 queries its "directory" attribute. The method getCanonicalPath() returns the firt column of output below. The method getAbsolutePath() returns the second column. The former method throws an exception that must be caught or passed on, the latter method does not. Note how main() is being called recursively in line 13, and, how an array of strings with a single element can be allocated and initialized in lines 4 and 13. [Source: Horstmann, vol 2, p69]

    1  import java.io.*;

    2  public class FindDirectories {
    3     public static void main( String[] args ) {
    4        if (args.length == 0) args = new String[] { ".." };
    5        File pathName = new File( args[0] );
    6        String[] fileNames = pathName.list();
    7        for (int i=0; i < fileNames.length; i++) {
    8           File f = new File( pathName.getPath(), fileNames[i] );
    9           if (f.isDirectory()) {
   10              try { System.out.println( f.getCanonicalPath() ); }
   11                 catch( IOException e ) { System.out.println( e ); }
   12              // System.out.println( f.getAbsolutePath() );
   13              main( new String[] { f.getPath() } );
   14  }  }  }  }

   15  // C:\jdk1.2b3\vlh\images          // C:\jdk1.2b3\vlh\.\images
   16  // C:\jdk1.2b3\vlh\audio           // C:\jdk1.2b3\vlh\.\audio
   17  // C:\jdk1.2b3\vlh\audio\sd        // C:\jdk1.2b3\vlh\.\audio\sd
   18  // C:\jdk1.2b3\vlh\audio\sd\ssd    // C:\jdk1.2b3\vlh\.\audio\sd\ssd
The next example recursively lists a directory in a TextArea widget. The user may supply a directory name on the command line, or allow the current directory will be used (lines 24 and 25). A File object is created on line 26, and then queried to see if it is a directory (line 27). The constructor sets up the desired GUI and calls recurse() (line 11). The entire contents of a directory can be retrieved with a call to list() (line 14). Each String returned can then be used to seed another File object and examined to see if it too is a directory (lines 19 and 20). Recursion is used to process each directory found (line 21). [Source: Roberts, p376]
    1  import java.awt.*;
    2  import java.io.File;
   
    3  public class RecursiveLister extends Frame {
    4     private TextArea ta;
   
    5     public RecursiveLister( File f ) {
    6        setSize( 300, 450 );
    7        setTitle( "Directory Lister" );
    8        ta = new TextArea();
    9        ta.setFont( new Font( "Monospaced", Font.PLAIN, 12 ) );
   10        add( BorderLayout.CENTER, ta );
   11        recurse( f, 0 );
   12     }
   13     private void recurse( File dirFile, int depth ) {
   14        String[] contents = dirFile.list();
   15        for (int i=0; i < contents.length; i++) {
   16           for (int spaces=0; spaces < depth; spaces++)
   17              ta.append( "   " );
   18           ta.append( contents[i] + "\n" );
   19           File child = new File( dirFile, contents[i] );
   20           if (child.isDirectory())
   21              recurse( child, depth+1 );
   22     }  }
   23     public static void main( String[] args ) {
   24        String path = ".";
   25        if (args.length > 0) path = args[0];
   26        File f = new File( path );
   27        if ( ! f.isDirectory()) {
   28           System.out.println( "Doesn't exist or not a directory: " + path );
   29           System.exit( 0 );
   30        }
   31        new RecursiveLister( f ).setVisible( true );
   32  }  }

The RandomAccessFile class

The class RandomAccessFile supports both reading and writing to random access files. A random access file behaves like a large array of bytes stored in the file system. There is a kind of cursor, or index into the implied array, called the file pointer; input operations read bytes starting at the file pointer and advance the file pointer past the bytes read. If the random access file is created in read/write mode, then output operations are also available. Output operations write bytes starting at the file pointer and advance the file pointer past the bytes written. Output operations that write past the current end of the implied array cause the array to be extended.

In the example below, line 5 creates a file (or opens an existing for over­writing. Instead of using Unicode characters, 26 bytes (that represent characters) are written to the file (line 6). Line 13 reopens the previous file and sets the file pointer to the fifth character. readByte() reads the fifth character, converts it to upper case, and increments the file pointer. writeByte() then over­writes the sixth character in the file (line 14). Lines 15 and 16 do what was probably intended in line 14 - read a character, convert it, and write it back on itself. Line 24 reopens the same file again (this time in read­only mode). Line 25 reads either: buf.length bytes, or, until end­of­file (EOF). Lines 40 and 43 demonstrate that the file size and contents are exactly what you would anticipate.

    1  import java.io.*;
   
    2  public class TestRandomAccess {
    3     public static void outputFile() {
    4        try {
    5           RandomAccessFile f = new RandomAccessFile("TestRA.dat","rw");
    6           for (int i=0; i < 26; i++) f.write( (byte) i+97 );
    7           f.close();
    8        } catch( IOException e ) { e.printStackTrace(); }
    9        System.out.println( "outputFile() done" );
   10     }
   11     public static void changeFile() {
   12        try {
   13           RandomAccessFile f = new RandomAccessFile("TestRA.dat","rw");
   14           f.seek( 4 );   f.writeByte( f.readByte() - 32 );
   15           f.seek( 20 );  byte b = (byte) (f.readByte() - 32);
   16           f.seek( 20 );  f.writeByte( b );
   17           f.close();
   18        } catch( IOException e ) { e.printStackTrace(); }
   19        System.out.println( "changeFile() done" );
   20     }
   21     public static void inputFile() {
   22        byte[] buf = new byte[80];
   23        try {
   24           RandomAccessFile f = new RandomAccessFile("TestRA.dat","r");
   25           f.read( buf );
   26           f.close();
   27        } catch( IOException e ) { e.printStackTrace(); }
   28        System.out.println("inputFile()  read --"
   29           + new String(buf).trim() +"--");
   30     }
   31     public static void main( String[] args ) {
   32        outputFile();
   33        changeFile();
   34        inputFile();
   35  }  }
   
   36  // outputFile() done
   37  // changeFile() done
   38  // inputFile()  read --abcdeEghijklmnopqrstUvwxyz--
   39  //
   40  // TESTRA   DAT            26  09-23-98 10:55p TestRA.dat
   41  //
   42  // C:> type TestRA.dat
   43  // abcdeEghijklmnopqrstUvwxyz
The next example shows how to use a random access file to append to an existing file. The length() method returns the size of the file (line 7). Then seek() can be used to advance the file pointer to the EOF. Line 8 writes characters '0' to '9'. length() reports the new size of the file in line 9, and the entire file is read in line 11.
    1  import java.io.*;

    2  public class TestRandomAccess {
    3     public static void main( String[] args ) {
    4        try {
    5           RandomAccessFile f =
    6                 new RandomAccessFile( "TestRandomAccess.dat", "rw" );
    7           f.seek( f.length() );
    8           for (int i=0; i < 10; i++) f.write( (byte) i+48 );
    9           byte[] buf = new byte[ (int) f.length() ];
   10           f.seek( 0 );
   11           f.read( buf );
   12           f.close();
   13           System.out.println( "--" + new String( buf ) + "--" );
   14        } catch( IOException e ) { e.printStackTrace(); }
   15  }  }

   16  // --abcdeEghijklmnopqrstUvwxyz0123456789--
   17  //
   18  // C:> type TestRandomAccess.dat
   19  // abcdeEghijklmnopqrstUvwxyz0123456789

The InputStream class

The abstract class InputStream declares methods for reading bytes from some "source". It is the base class for the raging horde of derived classes listed below.
   java.io.InputStream 
      java.io.ByteArrayInputStream 
      java.io.FileInputStream 
      java.io.FilterInputStream 
         java.io.BufferedInputStream 
         java.util.zip.CheckedInputStream 
         java.io.DataInputStream
         java.security.DigestInputStream 
         java.util.zip.InflaterInputStream 
            java.util.zip.GZIPInputStream 
            java.util.zip.ZipInputStream
         java.io.PushbackInputStream 
      java.io.ObjectInputStream
      java.io.PipedInputStream 
      java.io.SequenceInputStream 
      java.io.StringBufferInputStream 
Below, line 5 finds and opens the specified file for reading. Line 6 returns the number of bytes that can be read from this input stream without blocking. The while loop reads six bytes (buf.length) at a time until EOF is reached (designated by a return value of -1). Line 11 closes the input stream and releases any system resources associated with the stream.
    1  import java.io.*;

    2  public class TestInputStream {
    3     public static void main( String[] args ) {
    4        try {
    5           FileInputStream fis = new FileInputStream( "TestRandomAccess.dat" );
    6           byte[] buf = new byte[6];   int num;
    7           System.out.println( "Total bytes to read is " + fis.available() );
    8           while ((num = fis.read(buf)) > -1)
    9              System.out.print( "--" + new String(buf).substring(0,num) );
   10           System.out.println( "--" );
   11           fis.close();
   12        } catch( IOException e ) { e.printStackTrace(); }
   13     }
   14  }

   15  // Total bytes to read is 26
   16  // --abcdeE--ghijkl--mnopqr--stUvwx--yz--
Reading low­level bytes is nice, but inserting some higher­order intelligence in an input stream would be even nicer. Java's "filter" stream feature provides this capability. One filter provided by Java builds­up sequences of bytes into Java primitive types (like int or String) that can be read directly. This is the class DataInputStream (see the example in the OutputStream section that follows).

The example below demonstrates how to create your own filter. A filter inherits from the base class FilterInputStream (line 2). Since each filter is one element of a larger processing stream, a filter class must have a constructor that accepts an InputStream argument (line 3) and relays that argument to the base class (line 4). The derived class filter may override any methods it desires (lines 6 and 11). But, in order to not loose the default functionality provided in the base class, explicitly calling the base class's method will routinely be valuable (lines 7 and 12). Then the derived class can embellish that functionality as desired.

If you leave line 21 out, main() simply outputs the contents of TestFilterStream.dat to standard­out. Inserting an UpperFilterStream in the middle causes all lower case characters to be converted to upper case as they are "streamed out" to standard­out. Notice that the abstract class InputStream is used to declare the stream object reference, yet a derived class UpperFilterStream object is being assigned to it. Also notice that the 134 byte input file is being read 10 bytes at a time (line 23), and is being faithfully reconstituted on standard­out (lines 29-32).

    1  import java.io.*;

    2  class UpperFilterStream extends FilterInputStream {
    3     public UpperFilterStream( InputStream is ) {
    4        super( is );
    5     }
    6     public int read() throws IOException {
    7        int b = super.read();
    8        if (b > 96) b = b - 32;
    9        return b;
   10     }
   11     public int read(byte[] b) throws IOException {
   12        int ret = super.read( b );
   13        for (int i=0; i < b.length; i++)
   14           if (b[i] > 96) b[i] = (byte) (b[i] - 32);
   15        return ret;
   16  }  }

   17  public class TestFilterStream {
   18     public static void main( String[] args ) {
   19        try {
   20           InputStream is =
   21                 new UpperFilterStream(
   22                    new FileInputStream( "TestFilterStream.dat" ));
   23           byte[] buf = new byte[10];   int num;
   24           while ((num = is.read( buf )) > -1)
   25              System.out.print( new String(buf).substring(0,num) );
   26           is.close();
   27        } catch( IOException e ) { e.printStackTrace(); }
   28  }  }

   29  // THE QUICK BROWN FOX ...
   30  // WHEN IN THE COURSE OF HUMAN EVENTS ---
   31  // "NOW IS THE TIME FOR ALL GOOD MEN"
   32  // FOUR SCORE, AND TWENTY YEARS AGO,

The OutputStream class

The abstract class OutputStream is analogous to InputStream; it provides an abstraction for writing bytes to a "destination". There is almost a one­to­one mapping of input to output streams.
   java.io.OutputStream 
      java.io.ByteArrayOutputStream 
      java.io.FileOutputStream 
      java.io.FilterOutputStream 
         java.io.BufferedOutputStream 
         java.util.zip.CheckedOutputStream 
         java.io.DataOutputStream
         java.util.zip.DeflaterOutputStream 
            java.util.zip.GZIPOutputStream 
            java.util.zip.ZipOutputStream
         java.security.DigestOutputStream 
         java.io.PrintStream 
            java.rmi.server.LogStream 
      java.io.ObjectOutputStream
      java.io.PipedOutputStream 
The java.io package provides many layers of abstraction and functionality to choose from. These layers provide "plug compatibility" from the "upstream element" (i.e. lower­level abstraction) to the the "downstream element" (higher­level abstraction). The constructors of downstream elements should be passed the object reference of their immediately preceeding upstream element. This structuring mechanism is true for both input streams and output streams.

In the example below, a "two stage" output stream is writing the file TestInputStream.dat (lines 4-12); and then a "three stage" input stream is reading the same file (lines 13-22).

    1  import java.io.*;

    2  public class TestOutputInputStream {
    3     public static void main( String[] args ) {
    4        try {
    5           DataOutputStream dos =
    6                 new DataOutputStream(
    7                    new FileOutputStream( "TestInputStream.dat" ));
    8           dos.writeUTF( "The year " );
    9           dos.writeInt( 2000 );
   10           dos.writeUTF( " is coming!" );
   11           dos.close();
   12        } catch( IOException e ) { e.printStackTrace(); }
   13        try {
   14           DataInputStream dis =
   15                 new DataInputStream(
   16                    new BufferedInputStream(
   17                       new FileInputStream( "TestInputStream.dat" )));
   18           System.out.print( dis.readUTF() );
   19           System.out.print( dis.readInt() );
   20           System.out.println( dis.readUTF() );
   21           dis.close();  // outer close() only is OK [Gosling, p257]
   22        } catch( IOException e ) { e.printStackTrace(); }
   23  }  }

   24  // The year 2000 is coming!
A DataOutputStream object (line 6) allows an application to write primitive Java data types to an output stream in a machine­independent fashion. A DataInputStream object (line 15) provides the inverse functionality.

The class BufferedInputStream (line 16) implements a buffered input mechanism. By including this kind of input stage, an application can read bytes from a stream without necessarily causing a call to the underlying I/O subsystem for each byte read. The data is read by blocks into a buffer; subsequent reads can access the data directly from the buffer.

There is no writeString() method in class DataOutputStream. This is because Unicode's 16­bit encoding scheme has been found to be inadequate as a global text representation scheme. UTF is now used for input/output. [UTF - UCS Transformation Format. UCS - Universal Character Set] UTF encoding uses as many bits as needed to encode a character: fewer bits for smaller alphabets, more bits for larger Asian alphabets.

Lines 8-10 "stream out" two Strings and an integer, and lines 18-20 "stream" them back in. This is a lot more convenient that converting them to bytes on output and from bytes on input.

Note: some texts describe that each stream object should be closed individually, but Gosling's book seems to indicate that the outer­most stream will close() the inner streams (line 21).

The Reader class

The abstract class Reader is to character streams as the class InputStream is to byte streams. Reader streams are exclusively oriented to Unicode characters. The hierarchy of reader classes is listed below.
   java.io.Reader 
      java.io.BufferedReader 
         java.io.LineNumberReader 
      java.io.CharArrayReader 
      java.io.FilterReader 
         java.io.PushbackReader 
      java.io.InputStreamReader 
         java.io.FileReader 
      java.io.PipedReader 
      java.io.StringReader 
The example that follows uses the LineNumberReader class to display and number the lines of a file in a TextArea widget. The user supplies a file name in the TextField widget created on line 31. When the <Enter> key is pressed, the actionPerformed() method of the TL class (TextListener) is called (because of the registration performed on line 32). A FileReader object is created that finds and opens the specified file (line 11). That object is "wrapped" with a higher­level abstraction (the LineNumberReader class, line 12) that can return one line of text at a time (line 16), and, count the lines that are being returned (line 17). As lines are read, each is appended to the end of a StringBuffer object (line 18). [Note that appending to the TextArea widget itself is also an option - if it chooses to behave properly.] When EOF is reached, the StringBuffer is converted to a String and the contents of the TextArea widget are replaced (line 20).
    1  import java.awt.*;
    2  import java.awt.event.*;
    3  import java.io.*;
   
    4  public class FileLister extends Frame {
    5     private TextArea ta;
   
    6     class TL implements ActionListener {
    7        public void actionPerformed( ActionEvent e ) {
    8           TextComponent tc = (TextComponent) e.getSource();
   10           try {
   11              FileReader       fr  = new FileReader( tc.getText() );
   12              LineNumberReader lnr = new LineNumberReader( fr );
   13              StringBuffer     sb  = new StringBuffer();
   15              String s;
   16              while ((s = lnr.readLine()) != null) {
   17                 int ln = lnr.getLineNumber();
   18                 sb.append( (ln < 10 ? " " : "") + ln + ": " + s + "\n" );
   19              }
   20              ta.setText( sb.toString() );
   21              lnr.close();  // close lnr first because it was created last
   22              fr.close();   //    [Roberts, p386]
   11           } catch( FileNotFoundException f ) { f.printStackTrace(); } 
   23           } catch( IOException i )           { i.printStackTrace(); }
   24     }  }
   
   25     public FileLister() {
   26        setSize( 400, 500 );
   27        setTitle( "File Lister" );
   28        ta = new TextArea();
   29        ta.setFont( new Font( "Monospaced", Font.PLAIN, 12 ) );
   30        add( BorderLayout.CENTER, ta );
   31        TextField tf = new TextField();
   32        tf.addActionListener( new TL() );
   33        add( BorderLayout.SOUTH, tf );
   34     }
   35     public static void main( String[] args ) {
   36        new FileLister().setVisible( true );
   37  }  }

The Writer class

The abstract class Writer is to character streams as the class OutputStream is to byte streams. The hierarchy of writer classes is listed below.
   java.io.Writer 
      java.io.BufferedWriter 
      java.io.CharArrayWriter 
      java.io.FilterWriter 
      java.io.OutputStreamWriter 
         java.io.FileWriter 
      java.io.PipedWriter
      java.io.PrintWriter 
      java.io.StringWriter 
The following example uses FileWriter and BufferedWriter. Adding a buffering mechanism to input or output operations routinely improves performance because each read or write request does not have to be sent individually to the operating system's I/O subsystem. An unbuffered destination is created at line 12, and a buffered stream is built­up at lines 13-15. Both are timed as 50k integers are written to each, and the buffered stream comes out ahead by almost a factor of three.
    1  import java.io.*;
    2  import java.util.Date;

    3  public class TestWriter {
    4     public static int time( Writer w ) throws IOException {
    5        Date start = new Date();
    6        for (int i=0; i < 50000; i++)
    7           w.write( i );
    8        return (int) (new Date().getTime() - start.getTime());
    9     }
   10     public static void main( String[] args ) throws IOException {
   11        try {
   12           FileWriter     fw = new FileWriter( "TestWriter.one" );
   13           BufferedWriter bw =
   14                 new BufferedWriter(
   15                    new FileWriter( "TestWriter.two" ), 2000);
   16           System.out.println( "Write file unbuffered: " +
   17              time( fw ) + " msec" );
   18           System.out.println( "Write file   buffered: " +
   19              time( bw ) + " msec" );
   20           fw.close();
   21           bw.close();
   22        } catch( IOException e ) { e.printStackTrace(); }
   23  }  }

   24  // Write file unbuffered: 2580 msec
   25  // Write file   buffered: 880 msec

The Serializable interface

An object is "persistent" or "streamable" or "serializable" if it is possible to: encapsulate its internal implementation with some external representation, transport that representation across time or space, and restore or re­create the object "on the other side". A class makes itself "streamable" by implementing the java.io.Serializable interface. This interface has no methods; it only serves as a "marker" to indicate that the class can be considered for serialization.

"When an object is serialized, only the data of the object is preserved; methods and constructors are not part of the serialized stream. When a data member is an object, the data members of that object are also serialized." [Sun275, p12-125]   static and transient data members cannot be serialized. [Gosling, p261]

All subtypes of a serializable class are themselves serializable. Some classes are not serializable because the nature of the data they represent is constantly changing. If a serializable object contains a reference to a non­serializable element, the entire serialization operation fails, and a NotSerializableException is thrown.

Classes that require special handling during the serialization and deserialization process must override special methods with these exact signatures:

   private void writeObject( java.io.ObjectOutputStream out )
      throws IOException
   private void readObject( java.io.ObjectInputStream in )
      throws IOException, ClassNotFoundException
These methods do not need to concern themselves with the state belonging to their superclasses or subclasses.

The Externalizable interface extends Serializable. A class that implements Externalizable takes complete control over its serialized state. It even assumes responsibility for all state of its superclasses and versioning issues. The Externalizable interface has two methods:

   public void writeExternal( java.io.ObjectOutput out )
      throws IOException
   public void readExternal( java.io.ObjectInput in )
      throws IOException, ClassNotFoundException
Object serialization mechanisms use the Serializable and Externalizable interfaces. Object persistence mechanisms may use them also. Each object to be stored is tested for the Externalizable interface. If the object supports it, the writeExternal() method is called. If the object does not support Externalizable and does implement Serializable, the object should be saved using an ObjectOutputStream.

When an Externalizable object is to be reconstructed, an instance is created using the public no­argument constructor and the readExternal() method called. Serializable objects are restored by reading them from an ObjectInputStream.

The example below demonstrates a simple stream­out and stream­in sequence. The class TestSerialize has two data members: a String object and an int array (lines 3 and 4). Its constructor initializes these data members (lines 5-9). The static method outputSerialize() creates an instance of class TestSerialize (line 16), opens an ObjectOutputStream on to a FileOutputStream, and "serializes" the object on to the stream (line 20). The static method inputSerialize() opens an ObjectInputStream on to a FileInputStream that maps to the previous output file, and then "unserializes" the object off of the stream (line 30). The static method print() is a convenience function for reporting the state of an int array argument (lines 10-14, 23, and 33).

    1  import java.io.*;
   
    2  public class TestSerialize implements Serializable {
    3     public String str;
    4     public int[]  arr;
   
    5     public TestSerialize() {
    6        str = new String( "test_of_Serializable" );
    7        arr = new int[9];
    8        for (int i=0; i < 9; i++) arr[i] = i+1;
    9     }
   10     public static String print( int[] arr ) {
   11        StringBuffer sb = new StringBuffer();
   12        for (int i=0; i < arr.length; i++) sb.append( arr[i] + "_" );
   13        return sb.toString();
   14     }
   15     public static void outputSerialize() {
   16        TestSerialize obj = new TestSerialize();
   17        try {
   18           FileOutputStream   f = new FileOutputStream( "TestSerialize.dat" );
   19           ObjectOutputStream s = new ObjectOutputStream( f );
   20           s.writeObject( obj );
   21           s.close();
   22        } catch( IOException e ) { e.printStackTrace(); }
   23        System.out.println( "out obj is " + obj.str + "--" + print(obj.arr) );
   24     }
   25     public static void inputSerialize() {
   26        TestSerialize obj = null;
   27        try {
   28           FileInputStream   f = new FileInputStream( "TestSerialize.dat" );
   29           ObjectInputStream s = new ObjectInputStream( f );
   30           obj = (TestSerialize) s.readObject();
   31           s.close();
   32        } catch( Exception e ) { e.printStackTrace(); }
   33        System.out.println( "inn obj is " + obj.str + "--" + print(obj.arr) );
   34     }
   35     public static void main( String[] args ) {
   36        outputSerialize();
   37        inputSerialize();
   38     }
   39  }
   
   40  // out obj is test_of_Serializable--1_2_3_4_5_6_7_8_9_
   41  // inn obj is test_of_Serializable--1_2_3_4_5_6_7_8_9_
   42  //
   43  // TESTSE~1 DAT           154  09-23-98 10:06p TestSerialize.dat