Files and Stream-based Input and Output
At some point, every programmer needs to deal with reading data from
some source (a file, the keyboard, a URL, etc.), or writing data to
some destination (another file, the screen, etc.). These basic
input and output capabilities (I/O) are used in most real-world
programs.
This page describes the basics of reading and writing data one character
at a time or one line at a time in Java:
Basic Input and Output Concepts
Java provides an extensive library of classes for managing input and
output of all forms of data. These classes are built around a
stream model. You can think of a stream as a sequence
of characters (in the case of textual data) or bytes (in the case
of binary data) that can be accessed in order.
Input operations are framed in terms of reading from a stream in
a three-step process:
open the stream,
read data items from the stream front to back in sequence, and
close the stream.
Output operations are framed in terms of writing to a stream in
a three-step process:
open the stream,
write data onto the end of the stream in sequence, and
close the stream.
To use Java's input/output classes, make sure that in addition to
importing the course-specific cs1705 package, also import
the java.io package:
import cs1705.*;
import java.io.*;
Dealing with Exceptions
When your program deals with external factors, like files on disk, sometimes
errors or other conditions that cannot be predicted in advance may
arise. For example:
A person may type in incorrect information for your program to read.
A file that was around before may have accidentally been deleted.
There may not be enough hard disk space remaining to write
all the data you intend.
A person may enter an incorrect file name for your program to use--one
for a file that does not exist.
As a result, java uses exceptions to indicate when something
goes wrong. An exception is an object "thrown" into the middle of
your program that interrupts the normal flow of execution. Normally,
an exception causes the remainder of what you were doing to be aborted
and skipped, and program execution instead picks up with an exception
handler that has a chance to take appropriate action.
While we are not going to study exceptions in this course, they are a fact
of life embedded deep in Java's I/O library. Fortunately, there is
a simple way to deal with this issue for the input and output tasks
we will perform in this class.
Normally, the three-step process (open stream, read or write, close)
you are following should be enclosed in the following Java syntax:
try
{
// open stream
...
// operate on stream contents
...
// close stream
}
catch ( Exception e )
{
e.printStackTrace();
}
If you try to open an I/O stream without this code, BlueJ will
complain:
unreported exception java.io.IOException; must be caught or declared to be thrown
Opening a Stream for Output
In this class, we will only deal with textual, human-readable output.
The main class we will use for generating output is Java's
PrintWriter class, from the java.io package.
Creating a PrintWriter is simple:
PrintWriter outStream = IOHelper.createPrintWriter( "output.txt" );
This line declares a new name, outStream and creates
a new PrintWriter object that sends output to a brand
new file in the file system. The PrintWriter
is created using an operation from the
IOHelper
class in the cs1705 package.
The createPrintWriter() operation used here takes just one
argument: the name of the file to write. The IOHelper class
opens files using path names relative to your BlueJ project directory, so the
output.txt file produced by writing to outStream
will be located right in that directory. If a file with the specified
name already exists, it will be overwritten with new contents.
The
IOHelper
class also offers other methods for creating PrintWriter
objects, including a version of createPrintWriter() that
allows you to append to an existing file rather than overwriting it,
and createConsoleWriter() to create a PrintWriter
object that writes to the terminal window (or to the screen, in a
stand-alone program).
Writing to an Output Stream
Three basic methods provided by PrintWriter
objects provide virtually all of the output capabilities you will need
in this course:
<stream>.print( <value> ); writes
the specified <value> to the given <stream>.
There are actually many versions of this method that support every
possible type of <value> you might want to print.
<stream>.println( <value> );
writes the specified <value> to the given
<stream>, and then follows it by writing a "line terminator"
to mark the end of the current line (Java writes an appropriate line
termination character sequence based on the current operating system's
text file format conventions). As with print() , you can
provide any type of value to println() . You can even call
println() without giving any argument at all, for example,
to terminate the current line after several previous print()
messages.
<stream>.write( <value> ); writes
a single character specified by an integer <value>.
This operation is most often used when you are producing output
one character at a time, rather than in larger chunks. However, If you
pass an entire String value to write() instead
on an int value, then the entire string will be written
to the PrintWriter() just as if you had used
print() .
For example:
outStream.print( "This is a message" );
outStream.println( "these words appear on the same line as those above" );
outStream.println( 100 / 2 ); // prints the value "50"
outStream.write( 65 ); // writes the letter 'A', whose ASCII code is 65
Closing a Stream
Once you have completed all of the operations you intend to carry out
on a given stream, the stream should be closed. Closing the
stream frees up operating system resources used to connect to and
communicate with the stream, and makes sure that any buffered data
you have written to the stream is flushed out to the physical device
involved (if any).
Closing a stream is easy:
outStream.close();
You should close both input streams and output streams this way.
In many simple programs, a good rule of thumb is to make sure that
the method that creates the stream should also be the one responsible
for closing it.
A Complete Output Example
We can put all these pieces together to show how to generate output to
a file, for example. Let's say we want to create a file called
output.txt containing some output from our program.
We can do it in one method like this (dont forget to import
java.io.* and cs1705.* in your class):
public void printResultFile( int result )
{
try
{
PrintWriter out = IOHelper.createPrintWriter( "output.txt" );
out.println( "This is the first line of output." );
out.print( "The result is: " );
out.print( result );
out.println();
out.close();
}
catch ( Exception e )
{
e.printStackTrace();
}
}
If called with a specific argument, like printResultFile( 42 ); ,
the method will produce a file called output.txt in your
BlueJ project directory containing these lines:
This is the first line of output.
The result is: 42
At other times, when there is a lot of output to produce, you may want
to place all the println() calls in one or more other
methods. Then you can pass a PrintWriter object as a parameter,
as in this example:
public void printResultFile()
{
try
{
PrintWriter out = IOHelper.createPrintWriter( "output.txt" );
printHeader( out );
printData( out );
out.close();
}
catch ( Exception e )
{
e.printStackTrace();
}
}
public void printHeader( PrintWriter outStream )
{
outStream.println( "This is the output for ..." );
// other output commands go here.
}
public void printData( PrintWriter outStream )
{
outStream.print( /* ... */ );
// more, as needed ...
}
Output with System.out
It turns out that printing to the terminal is such a common action that
Java provides a pre-initialized output stream just for that purpose,
called System.out . The advantage of
System.out is that it is already declared and always
ready for use, and your program is not responsible for closing it.
As a result, you can directly call print() ,
println() , or write() on System.out
anywhere you like.
This is often used as a simple but effective debugging technique,
allowing you to print out information as your program executes:
System.out.println( "beginning the code ..." );
...
if ( someCondition() )
{
System.out.println( "someCondition() is true" );
x = ...;
System.out.println( "x = " + x );
}
else
{
System.out.println( "someCondition() is false" );
y = ...;
System.out.println( "y = " + y );
}
Above, notice the way the plus operator (+ ) was used to
combine a textual string with another value to make a larger message.
This is a nice feature of Java--the plus operator works to "concatenate"
two strings into a larger string by placing one after the other. Further,
when you concatenate a string with any other value, the other value is
converted into a human-readable string representation first. Try
it, you'll like it!
The advantages of System.out are that it is
always ready and available, and it is so easy to use. Creation and
setup of this object is already provided for you by Java's virtual machine,
and cleanup is handled automatically as well. Further,
since the print() , println() , and
write() methods do not throw any exceptions (whether called
on System.out , or on any PrintWriter you
create yourself), you do not need to include a try/catch block
around your output messages.
There are also a few disadvantages to using System.out ,
however. First, when your code writes directly to System.out ,
it is difficult to change the "destination," say, in order to make the
code write to one or more named files, or make the code write into an
internal data structure so that the information can be used elsewhere
in the program. Second, System.out is not actually a
PrintWriter object. Instead, it is a
Java.io.PrintStream , which supports virtually all of the
same methods, but is not quite the same.
As a result, here are some recommendations for output in this course:
When you just want to produce simple messages in the terminal
window to help debug a problem with your code,
use System.out .
When you just want to interactively
prompt the user for some value(s), use System.out .
When your program is supposed to produce a series of output
lines in a file, use a PrintWriter .
When your program is supposed to produce a series of output
lines that may go either to the terminal window or to a file,
write one or more methods that use a PrintWriter
provided as a parameter. You can always call such a method and
provide it with a PrintWriter produced by
IOHelper.createConsoleWriter() in order to produce
output on the screen. Alternatively, you can pass in a
PrintWriter connected to a file instead (or even one
connected to an internet socket for communicating with another
program on another machine!).
Opening a Stream for Input
The main class we will use for reading input is Java's
BufferedReader class, from the java.io package.
Creating a BufferedReader is simple:
BufferedReader inStream = IOHelper.createBufferedReader( "input.txt" );
This line declares a new name, inStream and creates
a new BufferedReader object that reads characteres from
a file in the file system. The Buffered
is created using an operation from the
IOHelper
class in the cs1705 package.
The createBufferedReader() operation used here takes just one
argument: the name of the file to read. The IOHelper class
opens files using path names relative to your BlueJ project directory, so
the file called input.txt should be located there. An
exception will be thrown if the given file cannot be found or the name
is incorrect. As with createPrintWriter() , you can
provide a fully qualified path name instead of a relative path name
if you desire.
The
IOHelper
class also offers other methods for creating BufferedReader
objects, including createBufferedReaderForURL() that
allows you to read from a URL,
and createKeyboardReader() to create a BufferedReader
object that reads from the terminal window via the keyboard.
Reading from an Input Stream
Two basic methods provided by BufferedReader
objects provide virtually all of the input capabilities you will need
in this course:
<stream>.read(); reads a single character
from the specified <stream> and returns that character
as an integer value. The value -1 is returned if
no more characters are available in the <stream> (i.e.,
you have reached the end of input).
<stream>.readLine(); reads all
the characters remaining on the current line of text from the
specified <stream>, skips over the line termination
character(s) in the <stream>, and returns all the
characters read (except the line termination character(s))
as a String value. The value null
is returned if no more characters are available in the
<stream> (i.e., you have reached the end of input).
To use these methods, normally you must decide how you are going to
process input: one character at a time, or one line at a time? Either
way, the result that you read in can be passed directly to
write() on a PrintWriter if needed.
For example:
int nextChar = inStream.read(); // Get one character as an int
if ( nextChar == -1 )
{
// then we are at the end of the stream, no more input is available
}
...
String thisLine = inStream.readLine(); // Get an entire line
if ( thisLine == null )
{
// then we are at the end of the stream, no more input is available
}
The == operator used in the if statements
above is a predicate that returns true when its left- and righthand
sides are equal.
Also, if you have programmed in another language before, note that
characters in Java are encoded using unicode, a 16-bit character
code. Programmers in other languages are probably more familiar
with ASCII, the American Standard Code for Information
Interchange, which is an 7-bit character code. Fortunately, the
first 128 codes in unicode are equivalent to the
entire ASCII character
set. For American users, ASCII values may thus be freely used when
reading and writing character-by-character without error, although
this approach does not directly extend to programs written for an
international audience.
A Complete Input Example
We can put all these pieces together to show how to read input from
a file one character at a time, for example. Let's say we want to
read the characters from a file called input.txt .
We can do it in one method like this (dont forget to import
java.io.* and cs1705.* in your class):
public void readChars()
{
try
{
BufferedReader in = IOHelper.createBufferedReader( "input.txt" );
int nextChar = in.read();
while ( nextChar != -1 )
{
// Do something with the character here
nextChar = in.read(); // read the next one from the stream
}
in.close();
}
catch ( Exception e )
{
e.printStackTrace();
}
}
The != operator used in the while statement
above is the opposite of == . It is a predicate that returns
true when its left- and righthand sides are not equal.
Instead, if we wanted to process the input stream one entire line at
a time, instead of character-by-character, we could do it this way:
public void readLines()
{
try
{
BufferedReader in = IOHelper.createBufferedReader( "input.txt" );
String nextLine = in.readLine();
while ( nextLine != null )
{
// Do something with the line here
nextLine = in.readLine(); // read the next one from the stream
}
in.close();
}
catch ( Exception e )
{
e.printStackTrace();
}
}
At other times, when there is a lot of output to produce, you may want
to place all the read() calls in one or more other
methods. Then you can pass a BufferedReader object
as a parameter:
public void processInputFile()
{
try
{
BufferedReader in = IOHelper.createBufferedReader( "input.txt" );
readHeader( in );
readData( in );
in.close();
}
catch ( Exception e )
{
e.printStackTrace();
}
}
public void readHeader( BufferedReader inStream )
throws Exception
{
int nextChar = inStream.read();
// other input commands go here.
}
public void readData( BufferedReader inStream )
throws Exception
{
String line = in.readLine();
// more, as needed ...
}
Note that here, there is only one try/catch block, and it is located
in processInputFile() . It describes what action to take
if an exception occurs, say, because the specified input file cannot
be found. In this case, it says to simply print an error message (that is
what e.printStackTrace(); means) and skip all other
actions associated with processInputFile() , which will
allow the remainder of your program to continue from that point.
We could have placed another try/catch block inside each of the
other methods as well--both readHeader() and
readData() . That is because each read() or
readLine() operation can throw an exception under
certain conditions (say, you are reading from a URL but the
internet connection is lost during the process). However, in this
case, we already have one try/catch block in processInputFile() ,
and the other two methods just implement part of the processing for this
same file.
As a result, we can add a line just before the opening brace of both
readHeader() and readData() that says
throws Exception . This line indicates that it is
possible for this method to produce an exception, in this case, one
produced by a call to read() or readLine() .
Without adding the throws Exception line, the compiler
would issue an error for both readHeader() and
readData() , since both call an input operation that
can throw an exception but neither includes a try/catch block. The
throws Exception line indicates that responsibility for
including a try/catch block is instead placed on whatever calls
readHeader() or readData() .
That means that any method that calls readHeader() or
readData() must do so inside a try/catch block, like
processInputFile() does. Thus, no matter which method
receives the exception, the exception handling action specified in
processInputFile() will be taken.
This is one important difference between a BufferedReader
and a PrintWriter . The basic operations on a
BufferedReader (creating, reading a character, reading
a line, or closing) can all throw exceptions when they fail.
For a PrintWriter , however, only creating one or closing
one can throw an exception; none of the print() ,
println() , or write() methods throw
exceptions. That means you do not need to add
throws Exception to methods that simply print to
PrintWriter objects that come in as parameters, but
you always have to include throws Exception in methods
that read from BufferedReader objects, unless those
actions are placed inside their own try/catch block.
A Complete Input/Output Example
Often, it is necessary to combine the processes of reading from
some source and writing to some destination. Here is a simple
example that copies an input file character by character:
import cs1705.*;
import java.io.*;
// -------------------------------------------------------------------------
/**
* Shows how to read/write a file one character at a time.
* @author Stephen Edwards
* @version 2003.09.27
*/
public class CopyFileByChar
{
// ----------------------------------------------------------
/**
* Copy the source file to the specified destination file.
* @param fromFile the name of the file to copy from
* @param toFile the name of the file to copy to
*/
public void copyFile( String fromFile, String toFile )
{
try
{
BufferedReader source = IOHelper.createBufferedReader( fromFile );
PrintWriter dest = IOHelper.createPrintWriter( toFile );
int thisChar = source.read();
while ( thisChar != -1 )
{
dest.write( thisChar );
thisChar = source.read();
}
source.close();
dest.close();
}
catch ( Exception e )
{
e.printStackTrace();
}
}
}
In this case, we can accomplish the same goal by transferring text
an entire line at a time:
import cs1705.*;
import java.io.*;
// -------------------------------------------------------------------------
/**
* Shows how to read/write a file one character at a time.
* @author Stephen Edwards
* @version 2003.09.27
*/
public class CopyFileByLine
{
// ----------------------------------------------------------
/**
* Copy the source file to the specified destination file.
* @param fromFile the name of the file to copy from
* @param toFile the name of the file to copy to
*/
public void copyFile( String fromFile, String toFile )
{
try
{
BufferedReader source = IOHelper.createBufferedReader( fromFile );
PrintWriter dest = IOHelper.createPrintWriter( toFile );
String line = source.readLine();
while ( line != null )
{
dest.println( line );
line = source.readLine();
}
source.close();
dest.close();
}
catch ( Exception e )
{
e.printStackTrace();
}
}
}
Reading from and Writing to Strings
[ Be sure you are using v1.3 or later of
cs1705.jar (released 9/27/03). Use the
BlueJ link from the course
home page for instructions on how to check your version or upgrade. ]
Once you have mastered the basics of PrintWriter s and
BufferedReader s, it is possible to apply that knowledge
to reading from or writing to all kinds of information sources, not
just files. For example, it is easy to create a
BufferedReader connected to a URL on the internet
(see the IOHelper.createBufferedReaderForURL() method).
One common need is the ability to read from a string of text in
memory, or write to another string of text. To read from a string
of text, use the IOHelper.createBufferedReaderForString()
method:
BufferedReader inStream =
IOHelper.createBufferedReaderForString( yourString );
Creating a BufferedReader this way is often very
useful for testing, since it allows you to write text content
directly in your test case and turn it into a stream for reading,
rather than having to go through the extra steps necessary to create
a separate file just for this purpose.
Similarly, you can write directly to a new string in memory. This
takes a few more steps:
StringWriter result = new StringWriter();
PrintWriter outStream = new PrintWriter( result );
...
outStream.println( "Your text goes here" );
...
outStream.close();
String finalContents = result.toString();
First, create a StringWriter , which collects the output as
your print it. Then use the StringWriter to create
a PrintWriter , and you are ready to go. Generate all
the output you want into the PrintWriter , and close it
when you are done. After closing the PrintWriter , you
can obtain a single String object representing the
entire sequence of characters you generated by calling the
StringWriter 's toString() method, as
shown above.
sectionHeader( 'For Fun: Displaying Output in a Web Browser' ) ?>
[ Be sure you are using v1.3 or later of
cs1705.jar (released 9/27/03). Use the
BlueJ link from the course
home page for instructions on how to check your version or upgrade. ]
The cs1705 package provides a utility class that makes
it easy to display information in a web browser. The
Browser
class provides a method called openFile() that allows
you to open a window with your web browser to display the selected
file. It also provides an openURL() method that does
the same thing for URLs. This makes it easy to display your
program's output in a web browser if you desire, as in this short
example:
try
{
String fileName = "example.html";
PrintWriter out = IOHelper.createPrintWriter( fileName );
out.println( "<html>" );
out.println( "<body>" );
out.println( "<h1>This Document Was Automatically Generated</h1>" );
out.println( "<p>It is very easy to generate and display HTML.</p>" );
out.println( "</body>" );
out.println( "</html>" );
out.close();
Browser.openFile( fileName );
}
catch ( Exception e )
{
e.printStackTrace();
}
|