Previous | Next | Trail Map | Writing Java Programs | Input and Output Streams


Using Input and Output Streams

This page illustrates how to use many of the java.io input and output streams through the use of several examples. Click on the links below to get to the section you're interested in:

Using Piped Streams

The java.io package contains two classes, PipedInputStream and PipedOutputStream, that implements the input and output components of a pipe. Pipes are used to channel the output from one program (or thread or code block) into the input of another.

Piped input and output streams are convenient for methods that produce output to be used as input by someone else. For example, suppose that you were writing a class that implemented various text utilities such as sorting, extracting unique lines, and reversing text. It would be nice if the output of one of these methods could be used as the input for another. Thus you could string a series of these methods together to perform some function. The pipe shown here uses reverse, sort, and reverse on a list of words to create a list of rhyming words.

[PENDING: change this to a picture]
list of words -> reverse -> sort -> reverse -> rhyming words
Without piped streams, you would have to create temporary files between each step:
[PENDING: change this to a picture]
list of words -> reverse -> tmpfile
tmpfile -> sort -> tmpfile2
tmpfile2 - > reverse -> rhyming words
Let's look at a class that implements the reverse and sort methods using piped streams and a test program that uses the reverse and sort methods in the pipe shown above to generate a list of rhyming words.

[PENDING: talk to dac about the rewrite of this example]

First, the StringUtils class contains two methods, reverse() and sort(), designed to be used in a pipe. Both reverse() and sort read data from an InputStream, process it (either reversing the strings or sorting them), and produce a PipedInputStream suitable for another method to read. Let's look in detail at reverse(); the sort() method is very similar to reverse() and doesn't warrant it's own discussion.

public static InputStream reverse(InputStream source) {
    PipedOutputStream pos = null;
    PipedInputStream pis = null;

    try {
        DataInputStream dis = new DataInputStream(source);
        String input;

        pos = new PipedOutputStream();
        pis = new PipedInputStream(pos);
        PrintStream ps = new PrintStream(pos);

        while ((input = dis.readLine()) != null) {
            ps.println(reverseString(input));
        }
        ps.close();
    } catch (Exception e) {
        System.out.println("StringUtils reverse: " + e);
    }
    return pis;
}
The reverse() method takes an InputStream which contains a list of strings to be reversed. reverse() maps a DataInputStream onto the source InputStream so that it can use the DataInputStream's readLine() method to read each line from the file. (DataInputStream is a filtered stream which must be attached to (mapped onto) an InputStream whose data is to be filtered when read. Working with Filtered Streams talks about this.)

Next, reverse() creates a PipedOutputStream and connects a PipedInputStream to it. Remember that a PipedOutputStream must be connected to a PipedInputStream. Then reverse() maps a PrintStream onto the PipedOutputStream so that it can use the PrintStream's println() method to write strings to the PipedOutputStream.

Now, reverse() reads the input line by line from the source stream, reverses the input line using the reverseString() method. and then writes the reversed string to the PipedOutputStream.

When reverse() closes the PrintStream, all the data written to the PrintStream gets flushed into the PipedOutputStream its mapped to. This flushes the data into the PipedInputStream connected to the PipedOutputStream. The PipedInputStream is now filled with a list of words that have been reversed and is primed for reading by another method, program or thread. reverse() returns the PipedInputStream for use by the calling program.

The sort() method follows the same pattern:

Calls to reverse() and sort() can be cascaded together so that the output from one method can be the input for the next method. In fact, the test program RhymingWords does just that. It cascades calls to reverse(), sort(), and then reverse() to generate a list of rhyming words:
DataInputStream words = new DataInputStream(new FileInputStream("words"));
InputStream rhymedWords = StringUtils.reverse(StringUtils.sort(StringUtils.reverse(words)));
When you run RhymingWords on this file of words you will see this output:
Java
interface
image
language
communicate
integrate
native
string
network
stream
program
application
animation
exception
primer
container
user
graphics
threads
tools
class
bolts
nuts
object
applet
environment
development
argument
component
input
output
anatomy
security
If you look closely you can see that "rhyming" words such as environment, development, argument and component are grouped together.

Using Streams to Read and Write Files

File streams are perhaps the easist streams to understand. Simply put, FileInputStream (FileOutputStream) represents an input (output) stream on a file that lives on the native file system. You can create a file stream from the filename, a File object or a FileDescriptor object. Use these streams to read data from or write data to files on the file system.

This small example uses the file streams to copy the contents of one file into another.

import java.io.*;

class FileStreamsTest {
    public static void main(String args[]) {
        try {
            File inputFile = new File("farrago.txt");
            File outputFile = new File("outagain.txt");

            FileInputStream fis = new FileInputStream(inputFile);
            FileOutputStream fos = new FileOutputStream(outputFile);
            int c;

            while ((c = fis.read()) != -1) {
               fos.write(c);
            }

            fis.close();
            fos.close();
        } catch (FileNotFoundException e) {
            System.err.println("FileStreamsTest: " + e);
        } catch (IOException e) {
            System.err.println("FileStreamsTest: " + e);
        }
    }
}
The FileStreamsTest program creates a FileInputStream from a File object with this code:
File inputFile = new File("farrago.txt"); 
FileInputStream fis = new FileInputStream(inputFile);
Note the use of the File object, inputFile, in the constructor. inputFile represents the named file, farrago.txt, on the native file system. This program only uses inputFile to create a FileInputStream on farrago.txt. However, the program could use inputFile to get information about farrago.txt such as its full path name.

Using Streams to Read and Write Memory Locations

Use ByteArrayInputStream and ByteArrayOutputStream to read and write 8-bit data. You create these streams on an existing byte array and then use the read() and write() methods to read from or write data to the array in memory.

Use StringBufferInputStream to read data from a StringBuffer. You create a StringBufferInputStream on an existing StringBuffer object and then use the read() methods to read from the StringBuffer as it lives in memory. This stream is similar to the ByteArrayInputStream which reads 8-bit data from a byte array in memory but StringBufferInputStream reads 16-bit Unicode data from a string buffer in memory.

[PENDING: write example for either ByteArrayInputStream and ByteArrayOutputStream or StringBufferInputStream. Why isn't there a StringBufferOutputStream?]

Using Streams to Concatenate Files

The SequenceInputStream creates a single input stream from multiple input sources. This example program, Concatenate, uses SequenceInputStream to implement a concatenation utility that sequentially concatenates files together in the order they are listed on the command line.

This is the main program of the Concatenate utility:

import java.io.*;

class Concatenate {
    public static void main(String args[]) {
        ListOfFiles mylist = new ListOfFiles(args);

        try {
            SequenceInputStream s = new SequenceInputStream(mylist);
            int c;

            while ((c = s.read()) != -1) {
               System.out.write(c);
            }

            s.close();
        } catch (IOException e) {
            System.err.println("Concatenate: " + e);
        }
    }
}
The first thing that Concatenate does is create a ListOfFiles object named mylist which is initialized from the command line arguments entered by the user. Each command line argument is the name of a file to concatenate with the others. mylist is used to initialize the SequenceInputStream which uses mylist to get a new InputStream for every filename listed by the user.
import java.util.*;
import java.io.*;

class ListOfFiles implements Enumeration {

    String listOfFiles[];
    int current = 0;

    ListOfFiles(String listOfFiles[]) {
        this.listOfFiles = listOfFiles;
    }

    public boolean hasMoreElements() {
        if (current < listOfFiles.length)
            return true;
        else
            return false;
    }

    public Object nextElement() {
        InputStream is = null;

        if (!hasMoreElements())
            throw new NoSuchElementException("No more files.");
        else {
            try {
                String nextElement = listOfFiles[current];
                current++;
                is = new FileInputStream(nextElement);
            } catch (FileNotFoundException e) {
                System.out.println("ListOfFiles: " + e);
            }
        }
        return is;
    }
}
ListOfFiles implements the Enumeration interface. You'll see how this comes into play as we walk through the rest of the program.

After the main() method creates the SequenceInputStream, it reads from it a byte at a time. When the SequenceInputStream needs an InputStream from a new source (such as for the first byte read or when it runs off the end of the current input stream), it calls nextElement() on the Enumeration object to get the next InputStream. ListOfFiles creates FileInputStream objects lazily, meaning that whenever SequenceInputStream calls nextElement() ListOfFiles opens a FileInputStream on the next filename in the list and returns the stream. When the ListOfFiles runs out of files to read (it has no more elements), nextElement() returns null, and the call to SequenceInputStream's read() method returns -1 to indicate the end of input.

Concatenate simply echos all the data read from the SequenceInputStream to the standard output.

Try this: Try running Concatenate on the farrago.txt and words.txt files used as input to other examples in this lesson.

See Also

java.io.PipedInputStream
java.io.PipedOutputStream

java.io.FileInputStream
java.io.FileOutputStream

java.io.ByteArrayInputStream
java.io.ByteArrayOutputStream

java.io.StringBufferInputStream java.io.SequenceInputStream


Previous | Next | Trail Map | Writing Java Programs | Input and Output Streams