Java I/O
Java I/O

 

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:

  1. open the stream,

  2. read data items from the stream front to back in sequence, and

  3. close the stream.

Output operations are framed in terms of writing to a stream in a three-step process:

  1. open the stream,

  2. write data onto the end of the stream in sequence, and

  3. 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 PrintWriters and BufferedReaders, 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.

[ 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();
    }
Computer Science 1054, Introduction to Programming in Java
D. Barnette