Monitoring Progress with Size Input Streams
Progress bars provide visual feedback on the progress of lengthy tasks, such as reading a stream of bytes stored in a file. In some cases, Java progress monitors cannot be used because the number of bytes that remain to be read is not available from the InputStream
object that the bytes are read from. This article introduces the SizeInputStream
class that enables reading progress to be monitored when the number of bytes to be read can be determined from sources other than an InputStream
object, such as from a URLConnection
object.
1. Introduction
Interactive applications should provide the user with feedback on the progress of lengthy tasks with a progress bar. As the task progresses, the length of the bar increases from left to right to indicate how much of the task has been completed and to enable users to estimate how much longer it will take to complete:
A typical example of the need for a progress bar is when reading a stream of bytes, such as the contents of a large file. The Java InputStream
class provides a generic interface for reading bytes from a variety of sources. Classes for reading bytes from files, pipes, and filters, for example, are implemented by sub-classing InputStream
. As well as providing methods to read one or more bytes from a stream, the InputStream
class also provides an int available()
method that returns the number of bytes that remain to be read from the stream.
Java also provides a ProgressMonitorInputStream
class that reads bytes from an InputStream
object and pops up a progress bar if reading the stream will take more than a specified minimum amount of time (Geary 1999, p. 281). The ProgressMonitorInputStream
class uses the available()
method of class InputStream
to determine how many bytes remain to be read from the stream. The length of the bar is calculated by subtracting the initial number of bytes that were available to read from the stream from the number of bytes that are left to read.
Some input stream classes, such as the ZipInputStream
class for reading bytes from files compressed in the Zip format, cannot know in advance how many bytes are available to read (see for example Java Bug Parade 4028605 and 4186776). In such cases, the available()
method will always return 1 which makes it impossible to monitor the progress of reading from such streams.
2. Size Input Streams
In some cases client code can determine the number of bytes to be read from an input stream other than by calling the available()
method. For example, if the bytes to be read can be accessed with a URL, the number of bytes can be determined by calling the getContentLength()
method of a URLConnection
object. The following Java code determines the number of bytes in a resource that is accessed with a URL.
When the number of bytes to be read can be determined, the SizeInputStream
class can be used to overcome the inability of an InputStream
object’s available()
method to return a useful byte count. The SizeInputStream
class wraps an InputStream
object and the number of bytes that can be read from it, as determined, for example, by using a URLConnection
object. The available()
method of a SizeInputStream
object always returns the correct number of bytes that remain to be read. By monitoring the progress of reading from a SizeInputStream
object, a ProgressMonitorInputStream
object can monitor the progress of reading from an InputStream
object, even if it’s available()
method does not return a useful byte count.
A SizeInputStream
object is constructed with an InputStream
to read bytes from, and the number of bytes that are available to read. The three methods to read bytes from a SizeInputStream
object, read()
, read(byte[] b)
, and read(byte[] b, int off, int len)
, read the requested number of bytes from the enclosed InputStream
and record the number of bytes read. The available()
returns the number of bytes left to read from the InputStream
by subtracting the number of bytes that have been read from the number of bytes passed to the constructor.
The following Java code creates a ProgressMonitorInputStream
with a SizeInputStream
object, which provides the stream to read from and determines the number of bytes to read.
The implementation of the SizeInputStream
class is shown below.
References
- Geary, D. M., Graphic Java 2 Mastering the JFC, Volume II: Swing, 3rd edition, Prentice-Hall, 1999.
- Java Bug Parade 4028605. ZipInputStream available() incorrect after EOF.
- Java Bug Parade 4186776. ZipInputStream available() gives different results w/ compressed stream.