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\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 } }
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 // 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
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 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.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.
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.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 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.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 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, 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 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