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]
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 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\ssdThe 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 } }
In the example below, line 5 creates a file (or opens an existing for overwriting. 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 overwrites 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 readonly mode). Line 25 reads either: buf.length bytes, or, until endoffile (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 // abcdeEghijklmnopqrstUvwxyzThe 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
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.StringBufferInputStreamBelow, 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 lowlevel bytes is nice, but inserting some higherorder intelligence in an input stream would be even nicer. Java's "filter" stream feature provides this capability. One filter provided by Java buildsup 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 standardout. Inserting an UpperFilterStream in the middle causes all lower case characters to be converted to upper case as they are "streamed out" to standardout. 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 standardout (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,
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.PipedOutputStreamThe java.io package provides many layers of abstraction and functionality to choose from. These layers provide "plug compatibility" from the "upstream element" (i.e. lowerlevel abstraction) to the the "downstream element" (higherlevel 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 machineindependent 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 16bit 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 outermost stream will close() the inner streams (line 21).
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.StringReaderThe 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 higherlevel 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 } }
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.StringWriterThe 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 builtup 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
"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 nonserializable 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, ClassNotFoundExceptionThese 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, ClassNotFoundExceptionObject 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 noargument constructor and the readExternal() method called. Serializable objects are restored by reading them from an ObjectInputStream.
The example below demonstrates a simple streamout and streamin 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