Introduction to Java InputStreams
One of the most fundamental components of the java.io package (and indeed
of all the Java I/O packages) is the InputStream class. Whether reading from
a normal file, network socket, memory or a compressed stream of data, the basic point
of call is often some subclass of InputStream.
What is an InputStream?
An InputStream is a reference to source of data (be it a file, network
connection etc), that we want to process as follows:
- we generally want to read the data as "raw bytes" and then write our own
code to do something interesting with the bytes;
- we generally want to read the data in sequential order: that is,
to get to the nth byte of data, we have to read all the preceding bytes
first, and we're not guaranteed to be able to "jump back" again once we've read
them.
We'll see later that if we want to read the bytes as characters,
then we can "wrap" the InputStream in a Reader
to decode the bytes into characters.
Getting and opening an input stream
Various subclasses of InputStream exist, depending on where we
want to read our data from. Here are some examples:
Data source | Type of input stream (InputStream subclass) | How to obtain |
a file | FileInputStream | new FileInputStream(f) |
an entry in a zip file | ZipFileInputStream (Internal class to ZipFile) | new ZipFile(f).getInputStream(entry) |
a network socket | SocketInputStream | new Socket(...).getInputStream() |
a network source referred to by URL | Depends on the protocol | new URL(url).openStream() |
a byte array | ByteArrayInputStream | new ByteArrayInputStream(b) |
Example types of InputStream in the standard Java libraries.
Note that in Java there's not really a notion of "opening" an InputStream itself. Once you have
constructed an InputStream in one way or another, then it is assumed that the underlying
data source (e.g. file) has already been opened if necessary, or that this will happen when you
attempt to read the first byte.
On the other hand, we'll see that it is generally important to
close the stream once we're done with it. The InputStream class
provides the close() method which we need to make sure we call once we're
done with the stream. We'll come back to this point in more detail when we
consider input stream error handling.
Reading bytes from the stream
Once we have obtained our stream, probably using one of the methods in the table above,
then we can call one of the read() methods defined by InputSteam:
Arguments | Action | Return value | End of stream return value |
None |
Read a single byte |
The next unsigned byte in the file, as an int. |
-1 |
a byte array |
Read bytes from the stream into the array, limited by availability and array size |
Number of bytes read into the array |
a byte array, offset and number of bytes to read |
Read bytes from the stream into the array, starting at the given offset and limited by availability and number of bytes specified |
Number of bytes read into the array |
Different read() methods provided by InputStream.
So for example, to read consecutive bytes from a file, we can write something as
follows:
import java.io.*;
File f = new File(dir, filename);
InputStream in = new FileInputStream(f);
int b;
do {
b = in.read();
if (b != -1) {
System.out.println("The next byte is " + b);
}
} while (b != -1);
in.close();
In real life, we can write the loop above a bit more succinctly and
idiomatically as follows:
int b;
while ((b = in.read()) != -1) {
...
}
Notice that although we know that we're dealing specifically with a FileInputStream,
we always refer to in simply as an instance of InputStream. It is
generally good practice to refer only to the most specific interface or base class that
we actually need: our routine will then work as is for any type of input stream.
Plus in some cases, for example reading from a zip file entry, we might not actually
know (or have any reason to care) what the specific flavour of InputStream actually
is.
The code above will get us the bytes out of the file (or other stream) in a very basic way.
It does have some problems, however, that we'll need to deal with on the following pages:
- For the time being, we aren't dealing with error handling: both
the read() method and the close() method can throw an IOException
(as can the constructor of FileInputStream if the file couldn't be found or
read for some reason). We also have the problem that if an I/O error does occur part way
through reading, the above code will omit the close() call.
- It's quite inefficient for any significant number of bytes. For every single byte,
we're going to make a separate operating system call. It would be more efficient to make
a single OS call to read multiple bytes. An option is to use one of the multi-byte
read() calls mentioned above. Sometimes that's not very convenient, however,
and we'll see that using the BufferedInputStream wrapper class can be easier.
- We need to use an extra wrapper class to read
character data from the stream instead of "raw" bytes.
If you enjoy this Java programming article, please share with friends and colleagues. Follow the author on Twitter for the latest news and rants.
Editorial page content written by Neil Coffey. Copyright © Javamex UK 2021. All rights reserved.