Based on portions of Chapters 13 and 14 in Peter van der Linde's
book "Just Java 2" 5th. edition
Java's IO can be compared to a set of pipes that can be connected together.
Given a data source or sink, one connects to it
using a first piece of pipe. It is then possible to connect a second pipe
to the first pipe which can provide additional functions
not available in the first pipe. It is even possible to connect a third
pipe.
There are several options available for the second and third pipe depending
on the functions of interest.
Besides the traditional IO operations, reading and writing data from and
to files, it is also possible in Java to "serialize" objects:
Take a snapshot of the current state of an object in memory and save it
to disk. Later, it is possible to read the object from disk and restore it
in main memory. This options can be used in conjunction with sockets and thus
it is feasible to send entire objects
across the network to other Java programs running in other computers.
We are going to cover several types of classes. The following table summarizes
them:
Input
Output
16-bit Characters
Readers
Writers
8-bit Characters and Binary Data
InputStreams
OutputStreams
File
The class File contains methods that allow the programmer to find information
about a particular File. Its constructors allow the programmer to crate
a File object by passing a String parameter containing the name of the file
and, optionally, the directory.
The class also contains methods to manipulate subdirectories (creation
and listings). Documentation
for the class File.
Output
Java programs access external data by instantiating a stream on the data.
The most common places to write data are:
A sequential file
A String
A pipe (used to connect to another process)
The system console
An array of characters
A URL for an HTTP GET/POST
A random access file
An array of bytes
A socket (to connect to a process in a different machine)
Some of these destinations have their own dedicated class whose constructor
returns a stream.
Other destinations have a getOutputStream() method . After the information
stream is opened, some other class is
"wrapped" on top to transfer the data. Wrapping is, in a sense, connecting
a different pipe to the pipe that connects to the
data destination. Additional functions will be provided by the wrapper
(the second pipe).
The functions provided by the wrapping class will work on the different
underlying IO destinations (socket, file, keyboard...).
Unicode uses two bytes for every character, while ASCII uses only one byte
per character.
Different classes are required to handle output with Unicode or ASCII.
Streams operate on bytes.
Readers/Writers operate on double-byte characters.
Writer Classes
There are four kind of Writer classes, which are derived from the abstract
class Writer:
The class Writer contains only five output methods. Hence, it is usual
to "wrap" the destination Class with another Writer class with more output
methods. The class PrintWriter
is such a class. See the class example1 in the set of examples available
in the book to see "wrapping" in action:
import java.io.*;
public class example1 {
public static void main(String[] args) {
// Declare an object of the class
FileWriter
FileWriter myFW = null;
try {
// Instantiate
the object myFW
myFW
= new FileWriter( "dogs.txt" );
} catch(IOException x) { System.err.println("IOExcpn:
" + x); }
// The object myPW of the class PrintWriter
"wraps" around the object myFW
PrintWriter myPW = new PrintWriter(
myFW );
// Now it is possible to use the
methods of the class PrintWriter to write out to the file dogs.txt
int i =101;
myPW.print( i );
myPW.println(" Dalmatians");
// It is a good custom to close the
files after one has finished writing to them.
myPW.close();
}
}
Other Writer Wrappers
There are two other Writer Wrappers available:
FilterWriter
: Can be used to act as a filter on characters being written. See example
2 in the book.
The following example from the book illustrates how to use FilterWriter,
a BufferedWriter and a PrintWriter:
In this example several wrappers are used.
import java.io.*;
class MyFilter extends java.io.FilterWriter {
public MyFilter(Writer w) { super(w); }
public void write(String s, int off, int len) throws
java.io.IOException {
// Replace the character 1 with the
character 2
s = s.replace('1', '2');
super.write(s, off, len);
}
public void write(char[] cbuf, int off, int len) throws
IOException {
String s= new String(cbuf);
this.write(s, off, len);
}
}
public class example2 {
public static void main(String args[]) {
FileWriter myFW = null;
try {
myFW
= new FileWriter( "dogs.txt" );
} catch(IOException x) { System.err.println("IOExcpn:
" + x); }
FilterWriter filter = new MyFilter(
myFW );
BufferedWriter BW = new BufferedWriter(filter);
PrintWriter myPW = new PrintWriter(
BW );
// PrintWriter myPW = new PrintWriter(
//
new BufferedWriter(
//
new myFilter(
//
new FileWriter( "dogs.txt" ) ) ) );
// This will print out 202 Dalmatians
because the 1s are being replace by 2s
myPW.println("101 Dalmatians");
myPW.close();
}
}
Output Streams
Output Streams can be used to write out binary values and 8-bit characters.
Depending on the destination, the following Output Streams are available:
This is extremely convenient and flexible because the IO operations to Sockets
and URL Connections will be the same as if one was writing to a file.
In the same fashion as there is a PrintWriter, there is a PrintStream
class which can be wrapped around the OutputStream classes. This class will
produce output printable ASCII bytes. Recall that PrintWriter will produce
Unicode characters.
When one needs to do binary I/O, the appropriate class is DataOutputStream.
When one wraps several classes, only the outermost class is used for writing.
The following example from the book shows how to create a binary file:
import java.io.*;
public class example3 {
public static void main(String[] args) {
FileOutputStream myFOS = null;
try {
myFOS = new FileOutputStream( "numbers.bin" );
DataOutputStream myDOS = new DataOutputStream( myFOS );
myDOS.writeInt(29019 );
myDOS.writeInt(3);
myDOS.writeInt(5);
myDOS.writeInt(67);
} catch(IOException x) { System.err.println("IOExcpn:
" + x); }
}
}
System.in, System.out and System.err
Unix and DOS open three file descriptors when the shell starts a new process:
in, out and err.
in takes input from the keyboard, while out and err direct output to the
screen.
In java System.out and System.err are predefined PrintStreams. They are
part of Java.lang.System.
System.in is an input stream.
When one uses
System.out.println(...);
one is using the System.out file descriptor.
It is possible to redirect these streams by using:
System.setErr(PrintStream err)
System.setIn(InputStream in)
System.setOut(PrintStream out)
Output Stream Wrappers
The following wrappers are available for output streams:
BufferedReader myBR = new
BufferedReader(
new FileReader(name) );
int c;
while((c = myBR.read())
!= -1) // read a char until EOF
myZOS.write(c);
// write the char we just read
myBR.close();
}
}
Input
The idea of streams is also used for input files.
Readers
Readers are used to read 16-bit characters.
The following classes of Readers can be used depending on the particular
source of data:
The most common wrapper around an InputStream is DataInputStream
which contains methods to read binary bytes.
Anything written with a DataOutputStream can be read back by a DataInputStream.
Exceptions
IOException
is a general class of Exception that can be thrown by IO operations. It has
a number of subclasses which describe in greater detail the particular case
that has occurred.
The following example, expandgz, expands a file compressed with gzip.
// Expand a .gz file into uncompressed form
// Peter van der Linden, August 2001
import java.io.*;
import java.util.zip.*;
public class expandgz {
public static void main (String args[]) throws Exception
{
if (args.length == 0) {
System.out.println("usage:
java expandgz filename.gz");
System.exit(0);
}
GZIPInputStream gzi = new GZIPInputStream(
new FileInputStream( args[0] ));
int to = args[0].lastIndexOf('.');
if (to == -1) {
System.out.println("usage:
java expandgz filename.gz");
System.exit(0);
}
String fout = args[0].substring(0,
to);
BufferedOutputStream bos = new
BufferedOutputStream(
new FileOutputStream(fout) );
System.out.println("writing "
+ fout);
int b;
do {
b = gzi.read();
if (b==-1)
break;
bos.write(b);
} while (true);
gzi.close();
bos.close();
}
}
Writing Objects to Disk
One of the most interesting features of I/O in Java is that it is possible
to take a snapshot of an object and save it to disk or send it to another
process though a socket.
The object whose state is being saved must implement the Serializable
interface. This is a very curious interface because it does not contain
any methods. Any class that claims to implement the Serializable interface
must include the
import java.io.*;
statement.
You will find a simple example of how to use Object Serialization here.
The key methods are readObject and writeObject.
Formatted String Output
Cobol programmers are used to very flexible options regarding the formatting
of output. Java provides a classe for this purpose: java.text.DecimalFormat
The following example illustrates its use:
import java.io.*;
import java.text.*;
// write binary data as formatted characters
public class exformat {
public static void main(String f[]) {
double db[] = { -1.1, 2.2, -3.33,
4.444, -5.5555, 6.66666};
DecimalFormat df = new DecimalFormat(
"#0.00;#0.00CR" );
for (int i=0; i<db.length;
i++) {
System.out.println(
df.format( db[i]) );
}
}
}