1. 引言
在 Java 的 I/O 体系中,InputStream 是所有字节输入流的抽象基类。无论是读取文件、网络数据,还是处理内存中的字节数组,InputStream 都是绕不开的入口。理解它的设计思想、核心方法与常见实现,是掌握 Java I/O 编程的关键一步。
本文将从 InputStream 的继承体系讲起,逐步深入到核心方法、常用子类、装饰器模式的应用,最后通过实战示例演示如何正确、高效地使用字节输入流。
2. InputStream 是什么
InputStream 是 java.io 包中用于表示字节输入流的抽象类。它定义了从数据源(文件、网络、内存等)读取字节数据的基本契约。
2.1 核心定位
- 字节流:以字节(8 位)为单位进行读写,适合处理二进制数据(图片、音频、视频、压缩包等)。
- 输入流:数据从外部流入程序,程序作为读取方。
- 抽象类:不能直接实例化,需要依赖具体子类或匿名内部类实现。
2.2 继承体系概览
#mermaid-svg-DJtfDhg9S8VLVVvd{font-family:"trebuchet ms",verdana,arial,sans-serif;font-size:16px;fill:#333;}@keyframes edge-animation-frame{from{stroke-dashoffset:0;}}@keyframes dash{to{stroke-dashoffset:0;}}#mermaid-svg-DJtfDhg9S8VLVVvd .edge-animation-slow{stroke-dasharray:9,5!important;stroke-dashoffset:900;animation:dash 50s linear infinite;stroke-linecap:round;}#mermaid-svg-DJtfDhg9S8VLVVvd .edge-animation-fast{stroke-dasharray:9,5!important;stroke-dashoffset:900;animation:dash 20s linear infinite;stroke-linecap:round;}#mermaid-svg-DJtfDhg9S8VLVVvd .error-icon{fill:#552222;}#mermaid-svg-DJtfDhg9S8VLVVvd .error-text{fill:#552222;stroke:#552222;}#mermaid-svg-DJtfDhg9S8VLVVvd .edge-thickness-normal{stroke-width:1px;}#mermaid-svg-DJtfDhg9S8VLVVvd .edge-thickness-thick{stroke-width:3.5px;}#mermaid-svg-DJtfDhg9S8VLVVvd .edge-pattern-solid{stroke-dasharray:0;}#mermaid-svg-DJtfDhg9S8VLVVvd .edge-thickness-invisible{stroke-width:0;fill:none;}#mermaid-svg-DJtfDhg9S8VLVVvd .edge-pattern-dashed{stroke-dasharray:3;}#mermaid-svg-DJtfDhg9S8VLVVvd .edge-pattern-dotted{stroke-dasharray:2;}#mermaid-svg-DJtfDhg9S8VLVVvd .marker{fill:#333333;stroke:#333333;}#mermaid-svg-DJtfDhg9S8VLVVvd .marker.cross{stroke:#333333;}#mermaid-svg-DJtfDhg9S8VLVVvd svg{font-family:"trebuchet ms",verdana,arial,sans-serif;font-size:16px;}#mermaid-svg-DJtfDhg9S8VLVVvd p{margin:0;}#mermaid-svg-DJtfDhg9S8VLVVvd .label{font-family:"trebuchet ms",verdana,arial,sans-serif;color:#333;}#mermaid-svg-DJtfDhg9S8VLVVvd .cluster-label text{fill:#333;}#mermaid-svg-DJtfDhg9S8VLVVvd .cluster-label span{color:#333;}#mermaid-svg-DJtfDhg9S8VLVVvd .cluster-label span p{background-color:transparent;}#mermaid-svg-DJtfDhg9S8VLVVvd .label text,#mermaid-svg-DJtfDhg9S8VLVVvd span{fill:#333;color:#333;}#mermaid-svg-DJtfDhg9S8VLVVvd .node rect,#mermaid-svg-DJtfDhg9S8VLVVvd .node circle,#mermaid-svg-DJtfDhg9S8VLVVvd .node ellipse,#mermaid-svg-DJtfDhg9S8VLVVvd .node polygon,#mermaid-svg-DJtfDhg9S8VLVVvd .node path{fill:#ECECFF;stroke:#9370DB;stroke-width:1px;}#mermaid-svg-DJtfDhg9S8VLVVvd .rough-node .label text,#mermaid-svg-DJtfDhg9S8VLVVvd .node .label text,#mermaid-svg-DJtfDhg9S8VLVVvd .image-shape .label,#mermaid-svg-DJtfDhg9S8VLVVvd .icon-shape .label{text-anchor:middle;}#mermaid-svg-DJtfDhg9S8VLVVvd .node .katex path{fill:#000;stroke:#000;stroke-width:1px;}#mermaid-svg-DJtfDhg9S8VLVVvd .rough-node .label,#mermaid-svg-DJtfDhg9S8VLVVvd .node .label,#mermaid-svg-DJtfDhg9S8VLVVvd .image-shape .label,#mermaid-svg-DJtfDhg9S8VLVVvd .icon-shape .label{text-align:center;}#mermaid-svg-DJtfDhg9S8VLVVvd .node.clickable{cursor:pointer;}#mermaid-svg-DJtfDhg9S8VLVVvd .root .anchor path{fill:#333333!important;stroke-width:0;stroke:#333333;}#mermaid-svg-DJtfDhg9S8VLVVvd .arrowheadPath{fill:#333333;}#mermaid-svg-DJtfDhg9S8VLVVvd .edgePath .path{stroke:#333333;stroke-width:2.0px;}#mermaid-svg-DJtfDhg9S8VLVVvd .flowchart-link{stroke:#333333;fill:none;}#mermaid-svg-DJtfDhg9S8VLVVvd .edgeLabel{background-color:rgba(232,232,232, 0.8);text-align:center;}#mermaid-svg-DJtfDhg9S8VLVVvd .edgeLabel p{background-color:rgba(232,232,232, 0.8);}#mermaid-svg-DJtfDhg9S8VLVVvd .edgeLabel rect{opacity:0.5;background-color:rgba(232,232,232, 0.8);fill:rgba(232,232,232, 0.8);}#mermaid-svg-DJtfDhg9S8VLVVvd .labelBkg{background-color:rgba(232, 232, 232, 0.5);}#mermaid-svg-DJtfDhg9S8VLVVvd .cluster rect{fill:#ffffde;stroke:#aaaa33;stroke-width:1px;}#mermaid-svg-DJtfDhg9S8VLVVvd .cluster text{fill:#333;}#mermaid-svg-DJtfDhg9S8VLVVvd .cluster span{color:#333;}#mermaid-svg-DJtfDhg9S8VLVVvd div.mermaidTooltip{position:absolute;text-align:center;max-width:200px;padding:2px;font-family:"trebuchet ms",verdana,arial,sans-serif;font-size:12px;background:hsl(80, 100%, 96.2745098039%);border:1px solid #aaaa33;border-radius:2px;pointer-events:none;z-index:100;}#mermaid-svg-DJtfDhg9S8VLVVvd .flowchartTitleText{text-anchor:middle;font-size:18px;fill:#333;}#mermaid-svg-DJtfDhg9S8VLVVvd rect.text{fill:none;stroke-width:0;}#mermaid-svg-DJtfDhg9S8VLVVvd .icon-shape,#mermaid-svg-DJtfDhg9S8VLVVvd .image-shape{background-color:rgba(232,232,232, 0.8);text-align:center;}#mermaid-svg-DJtfDhg9S8VLVVvd .icon-shape p,#mermaid-svg-DJtfDhg9S8VLVVvd .image-shape p{background-color:rgba(232,232,232, 0.8);padding:2px;}#mermaid-svg-DJtfDhg9S8VLVVvd .icon-shape .label rect,#mermaid-svg-DJtfDhg9S8VLVVvd .image-shape .label rect{opacity:0.5;background-color:rgba(232,232,232, 0.8);fill:rgba(232,232,232, 0.8);}#mermaid-svg-DJtfDhg9S8VLVVvd .label-icon{display:inline-block;height:1em;overflow:visible;vertical-align:-0.125em;}#mermaid-svg-DJtfDhg9S8VLVVvd .node .label-icon path{fill:currentColor;stroke:revert;stroke-width:revert;}#mermaid-svg-DJtfDhg9S8VLVVvd :root{--mermaid-font-family:"trebuchet ms",verdana,arial,sans-serif;} InputStream(抽象基类)
FileInputStream
ByteArrayInputStream
FilterInputStream
ObjectInputStream
PipedInputStream
SequenceInputStream
BufferedInputStream
DataInputStream
PushbackInputStream
3. 核心方法解析
InputStream 定义了多个读取方法,理解它们的区别与适用场景至关重要。
3.1 核心读取方法
| 方法签名 | 返回值 | 说明 |
|---|---|---|
int read() |
读取的字节(0~255),流结束返回 -1 |
每次读取一个字节,效率较低 |
int read(byte[] b) |
实际读取的字节数,流结束返回 -1 |
批量读取到字节数组 |
int read(byte[] b, int off, int len) |
实际读取的字节数,流结束返回 -1 |
读取指定长度的字节到数组指定位置 |
long skip(long n) |
实际跳过的字节数 | 跳过并丢弃 n 个字节 |
int available() |
可无阻塞读取的字节数估计值 | 用于判断是否还有数据 |
void close() |
无 | 释放流占用的系统资源 |
3.2 单字节读取的陷阱
java
// 错误写法:返回值被强转为 char,导致中文乱码
int data = inputStream.read();
while (data != -1) {
System.out.print((char) data);
data = inputStream.read();
}
java
// 正确写法:按字节读取后交给字符串解码
byte[] buffer = new byte[1024];
int len;
while ((len = inputStream.read(buffer)) != -1) {
// 处理 buffer 中前 len 个字节
}
3.3 为什么 read() 返回 int 而不是 byte
read() 返回 int 而非 byte,是为了能够用 -1 表示流结束。如果返回 byte,其取值范围是 -128~127,无法区分「合法的字节值 255」和「流结束」这两种情况。
4. 常用实现类
4.1 FileInputStream:文件输入流
用于从文件系统读取字节数据。
java
import java.io.FileInputStream;
import java.io.IOException;
public class FileInputStreamDemo {
public static void main(String[] args) {
try (FileInputStream fis = new FileInputStream("data.bin")) {
byte[] buffer = new byte[1024];
int len;
while ((len = fis.read(buffer)) != -1) {
System.out.println("读取到 " + len + " 字节");
}
} catch (IOException e) {
e.printStackTrace();
}
}
}
4.2 ByteArrayInputStream:字节数组输入流
将内存中的字节数组包装为输入流,常用于测试或数据转换场景。
java
import java.io.ByteArrayInputStream;
public class ByteArrayInputStreamDemo {
public static void main(String[] args) {
byte[] data = {72, 101, 108, 108, 111};
try (ByteArrayInputStream bais = new ByteArrayInputStream(data)) {
int b;
while ((b = bais.read()) != -1) {
System.out.print((char) b);
}
}
}
}
4.3 BufferedInputStream:缓冲输入流
通过内部缓冲区减少底层系统调用次数,显著提升读取性能。
java
import java.io.BufferedInputStream;
import java.io.FileInputStream;
import java.io.IOException;
public class BufferedInputStreamDemo {
public static void main(String[] args) {
try (BufferedInputStream bis = new BufferedInputStream(
new FileInputStream("large.bin"))) {
byte[] buffer = new byte[4096];
int len;
while ((len = bis.read(buffer)) != -1) {
// 处理数据
}
} catch (IOException e) {
e.printStackTrace();
}
}
}
4.4 DataInputStream:数据输入流
允许以 Java 基本数据类型直接读取二进制数据。
java
import java.io.DataInputStream;
import java.io.FileInputStream;
import java.io.IOException;
public class DataInputStreamDemo {
public static void main(String[] args) {
try (DataInputStream dis = new DataInputStream(
new FileInputStream("record.dat"))) {
int id = dis.readInt();
double score = dis.readDouble();
String name = dis.readUTF();
System.out.println("ID: " + id + ", Score: " + score + ", Name: " + name);
} catch (IOException e) {
e.printStackTrace();
}
}
}
5. 装饰器模式:InputStream 的设计精髓
InputStream 体系是装饰器模式的经典应用。FilterInputStream 作为装饰器基类,持有被包装的 InputStream 引用,通过层层包装为原始流增加新能力。
java
// 装饰器模式示例:为文件流叠加缓冲和数据类型读取能力
InputStream fileStream = new FileInputStream("data.bin");
InputStream bufferedStream = new BufferedInputStream(fileStream);
DataInputStream dataStream = new DataInputStream(bufferedStream);
这种设计的好处是功能可以自由组合,且无需修改原有类。例如:
BufferedInputStream增加缓冲能力;DataInputStream增加基本类型读取能力;PushbackInputStream增加回推能力。
6. 实战:文件复制
综合运用 InputStream 与 OutputStream 实现文件复制。
java
import java.io.FileInputStream;
import java.io.FileOutputStream;
import java.io.IOException;
public class FileCopyDemo {
public static void copyFile(String src, String dest) throws IOException {
try (FileInputStream fis = new FileInputStream(src);
FileOutputStream fos = new FileOutputStream(dest)) {
byte[] buffer = new byte[8192];
int len;
while ((len = fis.read(buffer)) != -1) {
fos.write(buffer, 0, len);
}
}
}
public static void main(String[] args) throws IOException {
copyFile("source.bin", "target.bin");
System.out.println("文件复制完成");
}
}
7. 常见问题与最佳实践
7.1 必须关闭流
InputStream 占用系统资源,使用完毕后必须关闭。推荐使用 try-with-resources 语法,确保流自动关闭。
7.2 使用缓冲提升性能
频繁的单字节读取会导致大量系统调用。建议:
- 使用
BufferedInputStream包装; - 或使用
byte[]批量读取。
7.3 区分字节流与字符流
- 处理二进制数据(图片、视频、压缩包)→ 使用
InputStream; - 处理文本数据 → 使用
Reader(如InputStreamReader包装字节流)。
7.4 注意 available() 的局限
available() 返回的是估计值,不能作为循环结束的唯一判断依据。正确做法是判断 read() 是否返回 -1。
8. 总结
InputStream 是 Java 字节输入流的基石,理解其核心方法、常用子类与装饰器模式,能够帮助开发者写出高效、健壮的 I/O 代码。在实际开发中,应根据数据源类型和性能需求选择合适的实现类,并始终遵循「及时关闭、合理缓冲」的原则。