Input and Output – Java I/O: Part I

20.1 Input and Output

Java provides I/O streams as a general mechanism for dealing with data input and output. I/O streams implement sequential processing of data. An input stream allows an application to read a sequence of data, and an output stream allows an application to write a sequence of data. An input stream acts as a source of data, and an output stream acts as a destination of data. The following entities can act as both input and output streams:

  • A file—which is the focus in this chapter
  • An array of bytes or characters
  • A network connection

There are two categories of I/O streams:

  • Byte streams that process bytes as a unit of data
  • Character streams that process characters as a unit of data

A low-level I/O stream operates directly on the data source (e.g., a file or an array of bytes), and processes the data primarily as bytes.

A high-level I/O stream is chained to an underlying stream, and provides additional capabilities for processing data managed by the underlying stream—for example, processing bytes from the underlying stream as Java primitive values or objects. In other words, a high-level I/O stream acts as a wrapper for the underlying stream.

In the rest of this chapter we primarily explore how to use I/O streams of the standard I/O API provided by the java.io package to read and write various kinds of data that is stored in files.

20.2 Byte Streams: Input Streams and Output Streams

The abstract classes InputStream and OutputStream in the java.io package are the root of the inheritance hierarchies for handling the reading and writing of data as bytes (Figure 20.1). Their subclasses, implementing different kinds of input and output (I/O) streams, override the following methods from the InputStream and OutputStream classes to customize the reading and writing of bytes, respectively:

Figure 20.1 Partial Byte Stream Inheritance Hierarchies in the java.io Package

From the InputStream abstract class:

Click here to view code image

int read() throws IOException
int read(byte[] b) throws IOException
int read(byte[] b, int off, int len) throws IOException

Note that the first read() method reads a byte, but returns an int value. The byte read resides in the eight least significant bits of the int value, while the remaining bits in the int value are zeroed out. The read() methods return the value –1 when the end of the input stream is reached.

Click here to view code image

long transferTo(OutputStream out) throws IOException

Reads bytes from this input stream and writes to the specified output stream in the order they are read. The I/O streams are not closed after the operation (see below).

From the OutputStream abstract class:

Click here to view code image

void write(int b) throws IOException
void write(byte[] b) throws IOException
void write(byte[] b, int off, int len) throws IOException

The first write() method takes an int as an argument, but truncates it to the eight least significant bits before writing it out as a byte to the output stream.

Click here to view code image

void close() throws IOException       
Both
 InputStream
and
 OutputStream
void flush() throws IOException       
Only for
 OutputStream

A I/O stream should be closed when no longer needed, to free system resources. Closing an output stream automatically flushes the output stream, meaning that any data in its internal buffer is written out.

Since byte streams also implement the AutoCloseable interface, they can be declared in a try-with-resources statement (§7.7, p. 407) that will ensure they are properly closed after use at runtime.

An output stream can also be manually flushed by calling the second method.

Read and write operations on streams are blocking operations—that is, a call to a read or write method does not return before a byte has been read or written.

Many methods in the classes contained in the java.io package throw the checked IOException. A calling method must therefore either catch the exception explicitly, or specify it in a throws clause.

Table 20.1 and Table 20.2 give an overview of selected byte streams. Usually an output stream has a corresponding input stream of the same type.

Table 20.1 Selected Input Streams

FileInputStreamData is read as bytes from a file. The file acting as the input stream can be specified by a File object, a FileDescriptor, or a String file name.
FilterInputStreamThe superclass of all input filter streams. An input filter stream must be chained to an underlying input stream.
DataInputStreamA filter stream that allows the binary representation of Java primitive values to be read from an underlying input stream. The underlying input stream must be specified.
ObjectInputStreamA filter stream that allows binary representations of Java objects and Java primitive values to be read from a specified input stream.

Table 20.2 Selected Output Streams

FileOutputStreamData is written as bytes to a file. The file acting as the output stream can be specified by a File object, a FileDescriptor, or a String file name.
FilterOutputStreamThe superclass of all output filter streams. An output filter stream must be chained to an underlying output stream.
PrintStreamA filter output stream that converts a text representation of Java objects and Java primitive values to bytes before writing them to an underlying output stream, which must be specified. This is the type of System.out and System.err (p. 1255). However, the PrintWriter class is recommended when writing characters rather than bytes (p. 1247).
DataOutputStreamA filter stream that allows the binary representation of Java primitive values to be written to an underlying output stream. The underlying output stream must be specified.
ObjectOutputStreamA filter stream that allows the binary representation of Java objects and Java primitive values to be written to a specified underlying output stream.