20.2 Byte Streams
The java.io package defines abstract classes for basic byte input and output streams. These abstract classes are then extended to provide several useful stream types. Stream types are almost always paired: For example, where there is a FileInputStream to read from a file, there is usually a FileOutputStream to write to a file.
Before you can learn about specific kinds of input and output byte streams, it is important to understand the basic InputStream and OutputStream abstract classes. The type tree for the byte streams of java.io in Figure 20-1 shows the type hierarchy of the byte streams.
Figure 20-1 Type Tree for Byte Streams in java.io
All byte streams have some things in common. For example, all streams support the notion of being open or closed. You open a stream when you create it, and can read or write while it is open. You close a stream with its close method, defined in the Closeable [1] interface. Closing a stream releases resources (such as file descriptors) that the stream may have used and that should be reclaimed as soon as they are no longer needed. If a stream is not explicitly closed it will hold on to these resources. A stream class could define a finalize method to release these resources during garbage collection but, as you learned on page 449, that could be too late. You should usually close streams when you are done with them.
All byte streams also share common synchronization policies and concurrent behavior. These are discussed in Section 20.5.1 on page 515.
20.2.1 InputStream
The abstract class InputStream declares methods to read bytes from a particular source. InputStream is the superclass of most byte input streams in java.io, and has the following methods:
-
public abstract int
read()
throws IOException
- Reads a single byte of data and returns the byte that was read, as an integer in the range 0 to 255, not –128 to 127; in other words, the byte value is treated as unsigned. If no byte is available because the end of the stream has been reached, the value –1 is returned. This method blocks until input is available, the end of stream is found, or an exception is thrown. The read method returns an int instead of an actual byte value because it needs to return all valid byte values plus a flag value to indicate the end of stream. This requires more values than can fit in a byte and so the larger int is used.
-
public int
read(byte[] buf, int offset, int count)
throws IOException
- Reads into a part of a byte array. The maximum number of bytes read is count. The bytes are stored from buf[offset] up to a maximum of buf[offset+count-1]—all other values in buf are left unchanged. The number of bytes actually read is returned. If no bytes are read because the end of the stream was found, the value –1 is returned. If count is zero then no bytes are read and zero is returned. This method blocks until input is available, the end of stream is found, or an exception is thrown. If the first byte cannot be read for any reason other than reaching the end of the stream—in particular, if the stream has already been closed—an IOException is thrown. Once a byte has been read, any failure that occurs while trying to read subsequent bytes is not reported with an exception but is treated as encountering the end of the stream—the method completes normally and returns the number of bytes read before the failure occurred.
-
public int
read(byte[] buf)
throws IOException
- Equivalent to read(buf,0, buf.length).
-
public long
skip(long count)
throws IOException
- Skips as many as count bytes of input or until the end of the stream is found. Returns the actual number of bytes skipped. If count is negative, no bytes are skipped.
-
public int
available()
throws IOException
- Returns the number of bytes that can be read (or skipped over) without blocking. The default implementation returns zero.
-
public void
close()
throws IOException
- Closes the input stream. This method should be invoked to release any resources (such as file descriptors) associated with the stream. Once a stream has been closed, further operations on the stream will throw an IOException. Closing a previously closed stream has no effect. The default implementation of close does nothing.
The implementation of InputStream requires only that a subclass provide the single-byte variant of read because the other read methods are defined in terms of this one. Most streams, however, can improve performance by overriding other methods as well. The default implementations of available and close will usually need to be overridden as appropriate for a particular stream.
The following program demonstrates the use of input streams to count the total number of bytes in a file, or from System.in if no file is specified:
import java.io.*; class CountBytes { public static void main(String[] args) throws IOException { InputStream in; if (args.length == 0) in = System.in; else in = new FileInputStream(args[0]); int total = 0; while (in.read() != -1) total++; System.out.println(total + " bytes"); } }
This program takes a filename from the command line. The variable in represents the input stream. If a file name is not provided, it uses the standard input stream System.in; if one is provided, it creates an object of type FileInputStream, which is a subclass of InputStream.
The while loop counts the total number of bytes in the file. At the end, the results are printed. Here is the output of the program when used on itself:
318 bytes
You might be tempted to set total using available, but it won't work on many kinds of streams. The available method returns the number of bytes that can be read without blocking. For a file, the number of bytes available is usually its entire contents. If System.in is a stream associated with a keyboard, the answer can be as low as zero; when there is no pending input, the next read will block.
20.2.2 OutputStream
The abstract class OutputStream is analogous to InputStream; it provides an abstraction for writing bytes to a destination. Its methods are:
-
public abstract void
write(int b)
throws IOException
- Writes b as a byte. The byte is passed as an int because it is often the result of an arithmetic operation on a byte. Expressions involving bytes are type int, so making the parameter an int means that the result can be passed without a cast to byte. Note, however, that only the lowest 8 bits of the integer are written. This method blocks until the byte is written.
-
public void
write(byte[] buf, int offset, int count)
throws IOException
- Writes part of an array of bytes, starting at buf[offset] and writing count bytes. This method blocks until the bytes have been written.
-
public void
write(byte[] buf)
throws IOException
- Equivalent to write(buf,0, buf.length).
-
public void
flush()
throws IOException
- Flushes the stream. If the stream has buffered any bytes from the various write methods, flush writes them immediately to their destination. Then, if that destination is another stream, it is also flushed. One flush invocation will flush all the buffers in a chain of streams. If the stream is not buffered, flush may do nothing—the default implementation. This method is defined in the Flushable interface.
-
public void
close()
throws IOException
- Closes the output stream. This method should be invoked to release any resources (such as file descriptors) associated with the stream. Once a stream has been closed, further operations on the stream will throw an IOException. Closing a previously closed stream has no effect.The default implementation of close does nothing.
The implementation of OutputStream requires only that a subclass provide the single-byte variant of write because the other write methods are defined in terms of this one. Most streams, however, can improve performance by overriding other methods as well. The default implementations of flush and close will usually need to be overridden as appropriate for a particular stream—in particular, buffered streams may need to flush when closed.
Here is a program that copies its input to its output, translating one particular byte value to a different one along the way. The TranslateByte program takes two parameters: a from byte and a to byte. Bytes that match the value in the string from are translated into the value in the string to.
import java.io.*; class TranslateByte { public static void main(String[] args) throws IOException { byte from = (byte) args[0].charAt(0); byte to = (byte) args[1].charAt(0); int b; while ((b = System.in.read()) != -1) System.out.write(b == from ? to : b); } }
For example, if we invoked the program as
java TranslateByte b B
and entered the text abracadabra!, we would get the output
aBracadaBra!
Manipulating data from a stream after it has been read, or before it is written, is often achieved by writing Filter streams, rather than hardcoding the manipulation in a program. You'll learn about filters in Section 20.5.2 on page 516.
Exercise 20.1 : Rewrite the TranslateByte program as a method that translates the contents of an InputStream onto an OutputStream, in which the mapping and the streams are parameters. For each type of InputStream and OutputStream you read about in this chapter, write a new main method that uses the translation method to operate on a stream of that type. If you have paired input and output streams, you can cover both in one main method.