CS621 Input and Output


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:
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:
  1. FileWriter  
  2. CharArrayWriter
  3. StringWriter
  4. PipedWriter
Writer classes output Unicode (2 bytes) characters.

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:
  1. FilterWriter : Can be used to act as a filter on characters being written. See example 2 in the book.
  2. BufferedWriter: Used to improve performance
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:
There is no output stream to write to a String, because one should use a Writer class instead.

If one is going to use sockets or URLs, then it is possible to obtain an Output Stream by using the method getOutputStream.
Check the classes:
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:

Output Stream Wrappers


The following wrappers are available for output streams:

OutputStreamWriter converts an OutputStream to a Writer class. It is a bridge between the 8-bit byte world and the 16-bit character world.

The following program, Example 4 ,  shows how to create a ZIP file called code.zip with three files inside.

import java.io.*;
import java.util.zip.*;
public class example4 {
    // writing a zip archive
    static ZipOutputStream myZOS;

    public static void main(String args[]) throws IOException {

        myZOS = new ZipOutputStream (
                  new BufferedOutputStream (
                      new FileOutputStream("code.zip") ) );
        writeOneFile("example1.java");
        writeOneFile("example2.java");
        writeOneFile("example3.java");
        myZOS.close();
    }

    static void writeOneFile(String name) throws IOException {
         ZipEntry myZE = new ZipEntry(name);
         myZOS.putNextEntry(myZE);

         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:
It is difficult to separate one item of data from the next one in the input. A StringTokenizer can be very useful in these cases.

Again, it is possible to use wrapper classes. The following wrapper classes are available for readers:

Input Streams

If one is reading regular 8-bit characters, one uses InputStreams.

Similarly to  OutputStreams, there are three possible sources for InputStreams:
  1. FileInputStream
  2. ByteArrayInputStream
  3. PipedInputStream
And it is possible to obtain InputStreams from
  1. java.net.Socket : using the method getInputStream
  2. java.net.URLConnection
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.

Input Stream Wrappers


There are many InputStream wrappers. Among them:
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]) );
        }
    }
}