Lesson 13.1 Built-in Streams
Built-in Streams and Redirection
In this unit you'll learn about some of Java's support for I/O or Input/Output. Java I/O is a large subject in its own right, and we'll just have time to touch on some of the major subjects.
If you're interested in learning more about Java input and output, then I recommend Elliotte Rusty Harold's "Java I/O", published by O'Reilly.
Basic Streams
Before we start looking at code, let's cover a little terminology.
What are streams? Streams are abstract information flows that can be likened to physical streams:
- Data flows into your program from some external location or device. That external location or device is called a data source, or simply a source.
- Your program processes the data.
- Information flows out of your program to the screen or printer, or to a disk file. The location where your data is sent is called a datasink, or simply a sink.
The process of retrieving information from a source is called reading, and the process of sending data to a sink is called writing.
In Java, the classes that support this process are located in the package java.io.
A Look at java.io
In our exploration of Java's I/O classes, we're going to look at four major groups:
- those classes that read information
- those classes that write information
- those classes that do both
- those classes that deal with the file system
The classes that read data and those that write data are divided into two groups:
- the original byte-oriented streams introduced in Java 1.0, called the InputStream/OutputStream classes.
- the Unicode character-oriented streams introduced in Java 1.1, called the Reader/Writer classes.
In this lesson, we'll cover the byte-oriented streams, InputStream and OutputStream, along with their subclasses. We'll look at the character-oriented streams, Reader and Writer, in Lesson 4. We'll cover the File class when we cover file operations in Lesson 13.6.
Standard Streams
The InputStream class and the OutputStream class are the abstract superclasses for Java's byte-oriented streams.
|
What is an abstract Class?
|
| An abstract class is an "incomplete" class in the sense that not all of its methods are defined.
An abstract class is a little like an interface, but with an interface none of the methods are defined. The InputStream and OutputStream are abstract because the implementation details required for reading from one type of stream--say a network connection--may differ considerably from the details needed to read from another type of stream--say the keyboard.
Both types of streams, however, must respond to the read(byte) method. |
The OutputStream class contains one fundamental method:
The argument to the write() method, although declared as an int, is really an 8-bit unsigned byte. When you call this method, 8 raw, untranslated bits are written to the output stream.
The OutputStream class also defines two methods to write byte arrays, a method to flush an output stream, and a close() method. We'll examine these later when we look at file ouput streams.
The InputStream class likewise defines one fundamental method:
When you call this method, your InputStream object will read 8 bits of data and return those 8 bits as an unsigned int.
Since only 8 bits are read, the InputStream read() method always returns a value between 0 and 255, unless the end-of-input is encountered. In that case, the read() method returns -1.
Predefined Streams
When you first start a Java program, it automatically opens three streams. These streams are:
- Standard input: connected to the keyboard
- Standard output: connected to the monitor
- Standard error: connected to the monitor
As you'll see shortly, although the standard input and standard error streams are initially connected to the keyboard and screen, they needn't remain so connected.
Each of the three standard streams is connected to a Java object. Standard input is connected to System.in, standard output is connected to System.out, and standard error is connected to System.err.
To use the standard input and ouput streams there's no need to open any files, you just call the read() and write() methods defined in the InputStream and OutputStream classes.
Here's a simple console mode application that reads from standard input, and writes to standard output. Because both read() and write() can throw an IOException, the main() method uses the throwsIOException clause to avoid using try-catch.
BaseFilter.java |
import java.io.*;
public class BaseFilter
{
public static void main(String[ ] args)
throws IOException
{
int ch;
while ((ch = System.in.read()) != -1)
{
// process data here
System.out.write(ch);
}
}
} |
When you compile and run the BaseFilter application, the cursor will simply blink, and you might think that your program has stopped. That's not the case.
Remember that standard input is connected to the keyboard [or console] by default. The console [your "DOS" Window] is a line-oriented data source. It will not send any data to your program until you type ENTER. Type in some text and press ENTER; when you do so, the entire line is sent to your program which reads it one byte at a time and then echoes each byte back to the standard output device--the screen.
When you're finished entering data, you need to send the BaseFilter program the end-of-input signal. From the console you do that by pressing Ctrl+Z in Windows, and Ctrl+D in Unix and on the Mac.
|
BaseFilter on the Mac
|
| To run the BaseFilter program on the Mac, you must use the JBindery program as you've done with other applications.
When you run the JBindery application, it does not automatically connect the standard input stream with your console/message window as Unix and Windows do.
To configure JBindery so that System.in is connected to your console, go to the Command window and change the "Redirect stdin" drop-down list to "Message Window"
|
Standard Streams
Redirection is an operating-system facility that allows you to associate a stream with different sources and sinks. Your Java program is unaware when this occurs and continues to read from standard input and write to standard output.
On the Mac you use the JBindery tool--as mentioned in the sidebar--along with the two Redirection drop-down lists to redirect either standard input and/or standard output.
DOS/Unix Output Redirection
For DOS/Windows and Unix, you use some special redirection symbols when your run your program. Let's take a look at a few.
Under DOS/Unix, you can associate the standard output stream with another file by using the > or >> symbols like this:
java MyClass > Output.fil
java MyClass >> Output.fil |
In the first case, the data written to the standard output stream [System.out] is sent to the disk file Output.fil. If the file already exists, it is erased and replaced. In the second case the data is also written to the file Output.fil, but, if the file already exists, the data is written after the existing data in the file. You can also associate the standard ouput stream with another device using the same syntax:
java MyClass > PRN:
java MyClass > LPT1:
java MyClass /dev/lpr |
All of these send the standard output to the printer. The first two are for DOS, and the last one is for Unix.
Piping Output
Finally, you can associate the standard output of one program with the standard input of another program. You can do this by using the pipe character [|]. For instance:
sends the output of your program to the sort program. The command:
| java MyClass | grep Frogs |
looks through the output of your program to see if the word Frogs occurs. [Both of these are Unix examples, but will work under Windows/DOS provided the sort and grep programs are installed.]
Redirecting Input
You can also redirect input in the same way. If you have a text file containing data, you can process the data by reading from standard input if you use the redirect input symbol < .
This command opens the file Input.fil and associates it with the standard input stream. When a System.in.read() is encountered in your program, the data will be read from the file Input.fil, instead of from the keyboard.
Filters
The real advantage of using the predefined streams, along with redirection, is that you can easily create a set of small programs, called filters, that can be easily bolted together. This is quite common in the Unix world, where it was developed. It is much less common in the DOS and Mac worlds.
A filter program works just like the BaseFilter program, except it applies a consistent rule to each character that it processes before sending the data on to standard output.
Here is a simple filter called an uppercase filter. The lines that have been changed from BaseFilter are marked in red:
|
ToUpper.java
|
import java.io.*;
public class ToUpper
{
public static void main(String[] args)
throws IOException
{
int ch;
while ((ch = System.in.read()) != -1)
{
// process data here
char c = (char)ch;
System.out.write(Character.toUpperCase(c));
}
System.out.flush();
}
} |
Filters and flush()
In Java, the standard output stream does not automatically send all of its data to the output sink unless it encounters a new line character. That means it is possible for your filter programs to lose data, especially if your filter processes binary data.
To avoid this problem, you should end all of your filter programs by sending the flush() message to the System.out object, as you can see in the ToUpper program.
Something to Talk About
Here's a fun project. Since the InputStream and OutputStream classes read and write data a byte at a time, it is possible to "encrypt" the data by changing the bytes in a particular pattern.
For instance, if you use a filter to add 128 to each byte as it is written, the output data will be unintelligible. If you run the output through the same filter again, you'll be able to recover your original data.
Do you want to try it out? Download the file enc.dat from this directory and save it on your disk. Then, modify the BaseFilter program to decode the data. Tell us what it says.
Please continue to the next section of this lesson.
|