什么是 InputStream?
InputStream 是一个抽象类,位于 java.io 包中,它代表了一个字节输入流,即从某个数据源(如文件、网络连接、内存数组等)中读取字节数据的流。

你可以把它想象成一个水管,数据源是水库,InputStream 就是连接水库和水龙头(你的程序)的水管,数据(字节)像水一样,通过这根水管流进你的程序。
核心特点:
- 字节流:它以字节(
byte)为单位进行读取,而不是字符,这使得它可以处理任何类型的数据,包括文本、图片、音频、视频等二进制文件。 - 单向流动:数据只能从源流向程序(输入),不能反向。
- 阻塞式:在默认情况下,读取方法是阻塞的,如果流中没有数据,
read()方法会一直等待,直到有数据可读或流关闭。
InputStream 的核心方法
InputStream 提供了几个核心的读取方法,了解它们是掌握 InputStream 的关键。
| 方法 | 描述 | 返回值 |
|---|---|---|
read() |
从输入流中读取一个字节的数据,如果已到达流的末尾,则返回 -1。 |
int (返回的字节,范围 0-255) |
read(byte[] b) |
从输入流中读取最多 b.length 个字节的数据到字节数组 b 中,如果已到达流的末尾,则返回 -1。 |
int (实际读取的字节数) |
read(byte[] b, int off, int len) |
从输入流中读取最多 len 个字节的数据,存入字节数组 b 的从 off 开始的位置。 |
int (实际读取的字节数) |
close() |
关闭此输入流,并释放与该流关联的所有系统资源。 | void |
available() |
返回此输入流下一个方法调用可以不受阻塞地读取(或跳过)的估计字节数。 | int |
skip(long n) |
跳过和丢弃此输入流中数据的 n 个字节。 |
long (实际跳过的字节数) |
reset() |
将此流重新定位到最后一次对此输入流调用 mark 方法时的位置。 |
void |
mark(int readlimit) |
标记此输入流中的当前位置。 | void |
注意:read() 方法返回的是 int 类型,而不是 byte,这是因为需要用 -1 来表示流的末尾,而 byte 类型无法表示 -1,返回的 int 值只有在 0-255 范围内才代表一个有效的字节。

如何使用 InputStream(标准步骤)
使用 InputStream 的标准流程遵循一个模板模式,非常重要:
- 创建
InputStream对象:根据数据源的不同,创建其子类的实例(如FileInputStream,ByteArrayInputStream等)。 - 使用
try-with-resources语句包裹:这是强烈推荐的方式,它可以自动在代码块执行完毕后关闭流,即使发生了异常,也能确保资源被正确释放,避免了资源泄漏。 - 读取数据:在一个循环中,调用
read()方法读取数据,直到返回-1(表示流结束)。 - 处理数据:将读取到的字节数据转换为你需要的形式(如字符串、图片等)。
- 关闭流:由
try-with-resources自动完成。
代码示例
示例 1:从文件读取(最常见)
假设我们有一个名为 test.txt 的文件,内容为 "Hello, World!"。
FileInputStreamExample.java
import java.io.FileInputStream;
import java.io.IOException;
import java.io.InputStream;
public class FileInputStreamExample {
public static void main(String[] args) {
// 定义文件路径
String filePath = "test.txt";
// 使用 try-with-resources 语句,会自动关闭流
try (InputStream inputStream = new FileInputStream(filePath)) {
int byteRead;
// read() 方法会返回一个字节,如果到达末尾则返回 -1
System.out.println("开始读取文件内容(按字节读取):");
while ((byteRead = inputStream.read()) != -1) {
// 将读取到的 int 强制转换为 char 并打印
// 注意:这种方式只适合读取纯文本文件,且编码要匹配
System.out.print((char) byteRead);
}
System.out.println("\n文件读取完毕。");
} catch (IOException e) {
System.err.println("读取文件时发生错误: " + e.getMessage());
e.printStackTrace();
}
}
}
更高效的方式:使用 byte[] 缓冲区

逐个字节读取效率很低,因为每次调用 read() 都可能涉及一次 I/O 操作,更好的方式是使用字节数组作为缓冲区。
import java.io.FileInputStream;
import java.io.IOException;
import java.io.InputStream;
public class FileInputStreamBufferExample {
public static void main(String[] args) {
String filePath = "test.txt";
// 定义一个缓冲区,大小为 1024 字节
byte[] buffer = new byte[1024];
try (InputStream inputStream = new FileInputStream(filePath)) {
int bytesRead;
System.out.println("开始读取文件内容(使用缓冲区):");
// read(buffer) 会尝试将数据读入 buffer,返回实际读取的字节数
while ((bytesRead = inputStream.read(buffer)) != -1) {
// 将 buffer 中从 0 到 bytesRead 的内容转换为字符串并打印
// 使用 String 的构造函数指定编码,避免乱码
System.out.write(buffer, 0, bytesRead);
}
System.out.println("\n文件读取完毕。");
} catch (IOException e) {
System.err.println("读取文件时发生错误: " + e.getMessage());
e.printStackTrace();
}
}
}
示例 2:从内存中的字节数组读取 (ByteArrayInputStream)
import java.io.ByteArrayInputStream;
import java.io.IOException;
import java.io.InputStream;
public class ByteArrayInputStreamExample {
public static void main(String[] args) {
String sourceString = "This is a test string from memory.";
byte[] byteArray = sourceString.getBytes(); // 将字符串转为字节数组
// 使用 try-with-resources
try (InputStream inputStream = new ByteArrayInputStream(byteArray)) {
int byteRead;
System.out.println("从内存字节数组读取内容:");
while ((byteRead = inputStream.read()) != -1) {
System.out.print((char) byteRead);
}
System.out.println("\n读取完毕。");
} catch (IOException e) {
// 对于 ByteArrayInputStream,这个异常通常不会发生
e.printStackTrace();
}
}
}
示例 3:从网络读取 (SocketInputStream)
这是网络编程中的核心,当从 Socket 获取输入流时,你实际上是在读取从远程服务器发送过来的数据。
import java.io.IOException;
import java.io.InputStream;
import java.net.Socket;
public class NetworkInputStreamExample {
public static void main(String[] args) {
String hostname = "time-a.nist.gov";
int port = 13; // Daytime protocol port
try (Socket socket = new Socket(hostname, port);
InputStream inputStream = socket.getInputStream()) {
System.out.println("已连接到 " + hostname + ",正在接收时间信息...");
byte[] buffer = new byte[1024];
int bytesRead;
// NIST 服务器返回的是一个 ASCII 字符串,以换行符结尾
while ((bytesRead = inputStream.read(buffer)) != -1) {
System.out.write(buffer, 0, bytesRead);
}
System.out.println("\n连接已关闭。");
} catch (IOException e) {
System.err.println("网络通信出错: " + e.getMessage());
e.printStackTrace();
}
}
}
InputStream 的主要子类
InputStream 是一个抽象基类,日常开发中我们通常使用它的具体子类:
| 子类 | 数据源 | 用途 |
|---|---|---|
FileInputStream |
文件 | 从文件系统中读取文件内容。 |
ByteArrayInputStream |
字节数组 | 从内存中的字节数组读取数据。 |
StringBufferInputStream (已过时) |
String |
从 String 读取字节(不推荐,使用 StringReader 或 ByteArrayInputStream)。 |
PipedInputStream |
管道 | 用于线程间的通信,读取从 PipedOutputStream 写入的数据。 |
SequenceInputStream |
多个 InputStream |
将多个输入流连接成一个输入流,按顺序读取。 |
FilterInputStream |
其他 InputStream |
抽象类,作为“装饰器”的基类,为其他输入流提供附加功能,其子类包括: |
BufferedInputStream |
任何 InputStream |
为输入流提供缓冲,提高读取效率。强烈推荐使用! |
DataInputStream |
任何 InputStream |
允许应用程序以与机器无关的方式从底层输入流中读取基本 Java 数据类型(如 int, double, boolean 等)。 |
ObjectInputStream |
任何 InputStream |
用于“反序列化”,即从流中恢复 Java 对象。 |
AudioInputStream |
音频文件/流 | 读取音频格式的流数据。 |
最佳实践和常见问题
总是使用 try-with-resources
这是 Java 7 引入的黄金法则,它可以自动管理资源,避免因忘记调用 close() 而导致的资源泄漏。
优先使用缓冲流 (BufferedInputStream)
对于文件、网络等 I/O 操作,直接使用 FileInputStream 或 SocketInputStream 效率很低,将它们包装在 BufferedInputStream 中,可以大幅减少 I/O 操作次数,显著提升性能。
// 低效
try (InputStream is = new FileInputStream("large_file.zip")) { ... }
// 高效
try (InputStream is = new BufferedInputStream(new FileInputStream("large_file.zip"))) { ... }
处理中文等非 ASCII 文本
InputStream 是字节流,它不知道字符的编码,如果你用它来读取文本文件(如 .txt, .java, .xml),直接转换为 char 很可能会导致乱码。
正确做法:使用 InputStreamReader 将字节流转换为字符流,并指定正确的编码。
import java.io.*;
import java.nio.charset.StandardCharsets;
public class ReadTextWithEncoding {
public static void main(String[] args) {
String filePath = "test_utf8.txt"; // 假设这是一个 UTF-8 编码的文件
try (
// 1. 创建字节输入流
InputStream inputStream = new FileInputStream(filePath);
// 2. 使用 InputStreamReader 将字节流转换为字符流,并指定编码
Reader reader = new InputStreamReader(inputStream, StandardCharsets.UTF_8);
// 3. 使用 BufferedReader 提供高效的按行读取功能
BufferedReader bufferedReader = new BufferedReader(reader)
) {
String line;
System.out.println("按行读取文件内容(UTF-8编码):");
while ((line = bufferedReader.readLine()) != null) {
System.out.println(line);
}
} catch (IOException e) {
e.printStackTrace();
}
}
}
读二进制数据(图片、视频、PDF)用 InputStream,读文本数据用 InputStreamReader + BufferedReader。
检查 read() 的返回值
在 while 循环中,必须检查 read() 的返回值是否为 -1,这是判断流是否结束的唯一标准,忘记检查会导致无限循环。
| 特性 | 描述 |
|---|---|
| 本质 | 字节输入流的抽象基类,用于从源头读取字节数据。 |
| 核心方法 | read(), read(byte[]), close() |
| 使用模式 | try-with-resources + while ((read = is.read()) != -1) 循环。 |
| 性能优化 | 使用 BufferedInputStream 进行包装,提供缓冲机制。 |
| 文本处理 | InputStream 不适合直接读文本,应通过 InputStreamReader 转换为字符流。 |
| 资源管理 | 务必使用 try-with-resources,确保流被正确关闭。 |
掌握 InputStream 是 Java I/O 编程的基石,理解了它,你就能轻松处理文件、网络等各种数据源的输入问题。
