SUNY Geneseo, Department of Computer Science
by Doug Baldwin
Input and output in Java are provided by a collection of library classes that ultimately provide an astonishly uniform interface to all sorts of input and output, from the simplest to the most sophisticated. A Java program can read a Web page from the other side of the world in much the same way it reads a simple string of text from the keyboard; it can send text to a network server in much the same way it sends text to a user at the computer's console. The drawback to this powerful I/O system is that simple I/O (particularly input) seems more complicated than necessary at first. This document introduces programmers to some of the concepts underlying input and output in Java, and to some simple methods for reading and writing text.
The basic objects that Java programmers use to do input and output are known as streams, readers, and writers. Most input or output operations can also generate exceptions, and so programmers need to know how to work with exceptions in order to do input or output. Finally, most I/O-related classes are in the "java.io" package in the Java libraries. Programs that do text input or output must therefore import this package.
A stream is a sequence of data, from which programs can input values or into which programs can output values. The metaphor is a stream of water flowing through a pipe -- by opening a tap in the pipe you can receive (input) water, or you can pump (output) your own water into the pipe. Someone else at another position along the pipe can open their own tap to receive water you have pumped into the pipe, or can pump water into the pipe for you to receive. In computer terms, the people on the metaphorical pipe could be two programs communicating over a network, a program and some files, a program and a user at a console, etc. The water in the pipe represents the data that is being communicated.
Streams come in two basic forms: input streams, from which a program can read data, and output streams, into which a program can write data.
Streams are designed to transmit arbitrary kinds of data -- anything from raw bits out of a computer's memory to entire objects can flow through streams. Text, however, is considerably less arbitrary than this. The data values in text are always characters, text has conventions that arbitrary data doesn't have (e.g., text can be broken into lines), etc. Thus, rather than input or output text directly through a stream, it is often more convenient to put an intermediary object between the program and the stream. This intermediary can always read or write characters (so the programmer needn't always say explicitly "this is a character"), it can understand the special significance of end-of-line characters in input or automatically insert them into output, etc. These intermediary objects are readers (for input) and writers (for output).
A program that does text input typically creates a reader object connected to the data source (the connection involves a stream object, which may be created implicitly when creating the reader, or may be explicit). Then the program sends messages to the reader to retrieve characters or lines through the reader. To do text output, a program creates a writer connected to the data sink (again, the connection involves a stream, but it may be implicit in the writer). Then the program sends messages to the writer that make it deliver characters or strings to the data sink.
The following diagram illustrates the relationships between streams, readers or writers, and the code you are likely to write as a programmer needing to do text input or output.
Exceptions in Java are signals that something unexpected has happened. Typically the unexpected event is an error of some sort. Exceptions generally cause the part of the program that caused the unexpected event to exit immediately. Java programmers often speak of this as "throwing" an exception. Some other part of the program should handle, or "catch," the exception. For a longer introduction to exceptions, see the Guide to Exceptions at http://cs.geneseo.edu/~baldwin/reference/java-exceptions.http.
Input and output are fruitful sources of exceptions, because so much can go wrong with the external data source or sink. For example, disks and file systems can be too full to accept more data, files may not exist, programs at the other end of a network connection may crash, etc. Thus many I/O messages may generate exceptions, and programs that do I/O should be written to handle those exceptions. See below for examples of how programs can do this.
Nearly all the exceptions that can be generated by text input
and output are instances of subclasses of the library class IOException
.
Thus it is usually sufficient to handle IOException
s when you write
code that does text input or output.
The classes of objects that do input or output all come from the Java class libraries. They are all in a package named "java.io", which is not automatically imported into Java programs. Thus you need to explicitly import it into any source files you write that do input or output. The easiest way to do this is to import the whole package, by putting the statement
import java.io.*;
at the beginning of your source file(s).
The general strategy for doing text input is to create a reader
for the input source, and then send it read
or readLine
messages to input individual characters or lines. There are several kinds of
reader that you might use, the most common being a class named BufferedReader
.
A BufferedReader
is a kind of reader that groups,
or "buffers" characters as it reads them from the underlying stream.
Buffering makes reading more efficient, and allows BufferedReader
objects to deliver whole lines of text as strings.
The most common constructor for BufferedReader
s takes
another (unbuffered) reader as its parameter. This constructor initializes a
BufferedReader
to read and buffer characters from the reader provided
as a parameter. To kinds of reader commonly used as this parameter are FileReader
(for reading from files) and InputStreamReader
(for reading from
arbitrary input streams). Both are discussed in more detail below. For example,
here is how you might create a BufferedReader
that reads from the
standard keyboard input:
InputStreamReader unbuffered = new InputStreamReader( System.in );
BufferedReader keyboard = new BufferedReader( unbuffered );
Once you have created a BufferedReader
, you can read
either individual characters, or whole lines, from it.
To read a line of text, use the readLine
message.
This message has no parameters. It returns a string containing the next line
from the BufferedReader
. If there is no more unread text in the
BufferedReader
, readLine
returns null
.
The readLine
message may throw an IOException
if it
is unable to read for any reason other than being at the end of the reader.
Here is an example of reading a line from the keyboard
BufferedReader
created above:
try {
String inputLine = keyboard.readLine();
if ( inputLine != null ) {
// ... process "inputLine" ...
}
else {
System.err.println( "No more keyboard input." );
}
}
catch ( IOException error ) {
System.err.println( "Error reading keyboard: " + error );
}
Note that this example declares the variable that receives the
input line inside the try
block. Thus that variable is only defined
within the try
block, and so all processing of the input has to
happen within that block. This is only one of many ways of setting up the interaction
between a readLine
and the try
that handles errors
it might throw. You may combine the readLine
and try
in other ways in your own code when appropriate.
To read single characters from a BufferedReader
,
use the read
message. This message has no parameters. It returns
an integer, which contains the Unicode code for the next character from the
BufferedReader
. If there are no more unread characters in the BufferedReader
,
read
returns -1. You can cast non-negative integers returned by
read
to type char
for processing as characters. The
read
message may throw IOException
s if it can't read
for any reason other than being at the end of the input. For example, here is
code that reads a single character from the keyboard
BufferedReader
created above:
try {
int input = keyboard.read();
if ( input >= 0 ) {
char c = (char) input;
// ... process the input character in variable "c" ...
}
else {
System.err.println( "No more keyboard input." );
}
}
catch( IOException error ) {
System.err.println( "Error reading keyboard: " + error );
}
When you are through using a BufferedReader
, you
can break your program's connection to the underlying stream and data source
by sending the BufferedReader
a close
message. This
message takes no parameters, and has no return value. It may, however, throw
an IOException
if it cannot close the BufferedReader
for some reason. For example
try {
keyboard.close();
}
catch ( IOException error ) {
System.err.println( "Couldn't close keyboard reader: " + error );
}
As mentioned above, the constructor for BufferedReader
needs another reader as its parameter. One of the reader classes commonly used
for this purpose is InputStreamReader
, which is a reader for an
arbitrary input stream.
The simplest constructor for InputStreamReader
s takes
the stream you want to read as its only parameter.
Java automatically provides an input stream connected to the keyboard
in variable System.in
. Use this variable as a parameter to the
InputStreamReader
constructor in order to create a reader that
reads text that the user types. For example
InputStreamReader keyReader = new InputStreamReader( System.in );
Another reader class commonly used to create BufferedReaders
is FileReader
. This class represents readers connected to disk
files. Creating a FileReader
implicitly creates an input stream
to the file.
The simplest constructor for FileReader
s takes the
name of the file, as a String
, as its only parameter. This constructor
throws an IOException
if the file does not exist. For example,
to create a FileReader
connected to a file named "textfile.txt",
you could write
try {
FileReader textReader = new FileReader( "textfile.txt" );
// ... Read data from "textfile.txt" here ...
}
catch ( IOException error ) {
System.err.println( "Error processing the text file: " + error );
}
The general strategy for outputting text is to create a kind of writer known
as a PrintWriter
connected to the place you want to send your output,
and then to use print
or println
messages to output
the text. The print
and println
messages are exactly
the ones you use to print text to the console. In fact, System.out
,
the object that represents the console, is an instance of a class closely related
to PrintWriter
(class PrintStream
).
PrintWriter
is the simplest way to output text. This class provides
the standard print
and println
messages for printing
textual representations of just about every data type in Java. PrintWriter
objects also intercept any exceptions generated by the output operations and
handle them internally, so that clients needn't worry about handling I/O exceptions.
The simplest constructor for PrintWriter
simply takes another
writer as its only parameter.
When you are through writing data to a PrintWriter
, you should
send it a close
message. This message takes no parameters and has
no return return value. It breaks the connection between a program and a PrintWriter
,
and makes sure that all data sent to the PrintWriter
has actually
been delivered to the data sink.
As an example, here is a fragment of code that creates a PrintWriter
connected to file "textout.txt" and writes the message "Hello
file" to it. This example uses a class named FileWriter
(described
next) to connect to the file; all exception handling in this example is there
because the FileWriter
constructor may throw exceptions.
try {
FileWriter rawOut = new FileWriter( "textout.txt" );
PrintWriter out = new PrintWriter( rawOut );
out.println( "Hello file" );
out.close();
}
catch ( IOException error ) {
System.err.println( "Error writing to output file: " + error );
}
The easiest way to write text to a file is to create a FileWriter
object connected to the file, then create a PrintWriter
connected
to the FileWriter
. Like FileReader
, FileWriter
implicitly creates a stream connected to the file.
The simplest constructor for FileWriter
objects takes the file
name, in a String
, as its only parameter. This constructor may
throw an IOException
if it cannot create a writer for the file.
The PrintWriter
example above includes code that creates a FileWriter
connected to file "textout.txt."
To demonstrate Java text I/O in a more realistic context, here is a program that builds a text file by copying text from the keyboard to the file. The program prompts the user for the file's name, then copies all subsequent keyboard input to the file. The program stops copying when it either detects the end of the keyboard input, or receives a line containing only a "." (some computing environments provide a way for users to signal the end of keyboard input, the "." convention provides a way to end the input in systems that don't).
import java.io.*;
class TextIOExample {
public static void main( String args[] ) {
try { BufferedReader in = new BufferedReader( new InputStreamReader( System.in ) ); System.out.print( "Destination file name = " ); String fileName = in.readLine(); PrintWriter out = new PrintWriter( new FileWriter( fileName ) ); String textLine = in.readLine(); while ( textLine != null && ! textLine.equals(".") ) { out.println( textLine ); textLine = in.readLine(); } out.close(); in.close(); } catch ( IOException error ) { System.err.println( "Error making file:" ); System.err.println( "\t" + error ); } } }
Last Updated September 29, 2003