Appearance
InputStream
InputStream is the most basic input stream provided by the Java standard library. It is located in the java.io
package, which provides all synchronous IO functionalities.
One important point to note is that InputStream is not an interface but an abstract class, serving as the superclass for all input streams. One of the most important methods defined by this abstract class is int read()
, with the following signature:
java
public abstract int read() throws IOException;
This method reads the next byte from the input stream and returns the byte as an int
value (0~255). If the end of the stream is reached, it returns -1 to indicate that no further reading is possible.
FileInputStream
is a subclass of InputStream
. As the name suggests, FileInputStream
reads data from a file stream. The following code demonstrates how to read all bytes from a FileInputStream
completely:
java
public void readFile() throws IOException {
// Create a FileInputStream object:
InputStream input = new FileInputStream("src/readme.txt");
for (;;) {
int n = input.read(); // Repeatedly call read() until it returns -1
if (n == -1) {
break;
}
System.out.println(n); // Print the byte value
}
input.close(); // Close the stream
}
In a computer, resources like files and network ports are managed uniformly by the operating system. If an application opens a file for reading or writing, it should close it promptly to allow the operating system to release the resources. Otherwise, the application will consume more and more resources, wasting memory and affecting the operation of other applications.
Both InputStream
and OutputStream
can be closed using the close()
method. Closing a stream releases the corresponding underlying resources.
We must also note that errors may occur while reading or writing IO streams, such as a file not existing leading to a read failure, or lack of write permissions causing write failure. These underlying errors are automatically wrapped by the Java Virtual Machine as IOException
exceptions and thrown. Therefore, all code related to IO operations must correctly handle IOException
.
Upon careful observation of the code above, a potential issue is evident: if an IO error occurs during reading, the InputStream
cannot be closed properly, and resources cannot be released in a timely manner.
Thus, we need to use try ... finally
to ensure that the InputStream
can be correctly closed regardless of whether an IO error occurs:
java
public void readFile() throws IOException {
InputStream input = null;
try {
input = new FileInputStream("src/readme.txt");
int n;
while ((n = input.read()) != -1) { // Use while to read and check simultaneously
System.out.println(n);
}
} finally {
if (input != null) { input.close(); }
}
}
Writing the above code using try ... finally
may feel complicated; a better approach is to use the new try(resource)
syntax introduced in Java 7, which only requires writing the try statement and allows the compiler to automatically close the resources for us. The recommended code is as follows:
java
public void readFile() throws IOException {
try (InputStream input = new FileInputStream("src/readme.txt")) {
int n;
while ((n = input.read()) != -1) {
System.out.println(n);
}
} // The compiler automatically writes the finally block and calls close() here
}
In fact, the compiler does not specifically add automatic closure for InputStream
. The compiler only checks whether the object in try(resource = ...)
implements the java.lang.AutoCloseable
interface. If it does, the compiler automatically adds the finally
statement and calls the close()
method. Both InputStream
and OutputStream
implement this interface, so they can be used in try(resource)
.
Buffering
When reading streams, reading one byte at a time is not the most efficient method. Many streams support reading multiple bytes into a buffer at once. For file and network streams, utilizing a buffer to read multiple bytes at once is often significantly more efficient. InputStream
provides two overloaded methods to support reading multiple bytes:
java
int read(byte[] b): Reads several bytes and fills them into a byte[] array, returning the number of bytes read.
int read(byte[] b, int off, int len): Specifies the offset and maximum number of bytes to fill in the byte[] array.
When using these methods to read multiple bytes at once, you need to first define a byte[]
array as a buffer. The read()
method will attempt to read as many bytes as possible into the buffer, but it will not exceed the size of the buffer. The return value of the read()
method is no longer the byte's int
value but instead indicates how many bytes were actually read. If it returns -1, it indicates that there is no more data available.
The code for reading multiple bytes using a buffer is as follows:
java
public void readFile() throws IOException {
try (InputStream input = new FileInputStream("src/readme.txt")) {
// Define a buffer of 1000 bytes:
byte[] buffer = new byte[1000];
int n;
while ((n = input.read(buffer)) != -1) { // Read into the buffer
System.out.println("read " + n + " bytes.");
}
}
}
Blocking
When calling the read()
method of InputStream
to read data, we say that the read()
method is blocking. This means that for the following code:
java
int n;
n = input.read(); // Must wait for the read() method to return before executing the next line of code
int m = n;
When executing the second line of code, it must wait for the read()
method to return before proceeding. Since reading IO streams is generally much slower than executing regular code, it is impossible to determine how long the read()
method call will take.
InputStream Implementations
Using FileInputStream
, you can obtain an input stream from a file, which is a commonly used implementation of InputStream
. Additionally, ByteArrayInputStream
can simulate an InputStream
in memory:
java
import java.io.*;
public class Main {
public static void main(String[] args) throws IOException {
byte[] data = { 72, 101, 108, 108, 111, 33 };
try (InputStream input = new ByteArrayInputStream(data)) {
int n;
while ((n = input.read()) != -1) {
System.out.println((char)n);
}
}
}
}
ByteArrayInputStream
effectively transforms a byte[]
array into an InputStream
in memory. Although it is not commonly used in actual applications, it can be useful for testing when constructing an InputStream
.
For example, if we want to read all bytes from a file, convert them to char
, and concatenate them into a string, we can write:
java
public class Main {
public static void main(String[] args) throws IOException {
String s;
try (InputStream input = new FileInputStream("C:\\test\\README.txt")) {
int n;
StringBuilder sb = new StringBuilder();
while ((n = input.read()) != -1) {
sb.append((char) n);
}
s = sb.toString();
}
System.out.println(s);
}
}
To test the above program, a real text file must be placed on the local hard drive. However, if we slightly modify the code to extract a readAsString()
method:
java
public class Main {
public static void main(String[] args) throws IOException {
String s;
try (InputStream input = new FileInputStream("C:\\test\\README.txt")) {
s = readAsString(input);
}
System.out.println(s);
}
public static String readAsString(InputStream input) throws IOException {
int n;
StringBuilder sb = new StringBuilder();
while ((n = input.read()) != -1) {
sb.append((char) n);
}
return sb.toString();
}
}
Testing the String readAsString(InputStream input)
method becomes relatively simple, as it does not necessarily require passing a real FileInputStream
:
java
import java.io.*;
public class Main {
public static void main(String[] args) throws IOException {
byte[] data = { 72, 101, 108, 108, 111, 33 };
try (InputStream input = new ByteArrayInputStream(data)) {
String s = readAsString(input);
System.out.println(s);
}
}
public static String readAsString(InputStream input) throws IOException {
int n;
StringBuilder sb = new StringBuilder();
while ((n = input.read()) != -1) {
sb.append((char) n);
}
return sb.toString();
}
}
This illustrates the application of the principle of programming to abstractions: accepting the InputStream
abstract type rather than a specific FileInputStream
type, allowing the code to handle any implementation class of InputStream
.
Summary
The Java standard library's java.io.InputStream
defines the superclass
for all input streams:
FileInputStream
implements file stream input;ByteArrayInputStream
simulates byte stream input in memory.
Always use try(resource)
to ensure that InputStream
is closed correctly.