Java的InputStream
是所有字节输入流的抽象基类,位于java.io
包中,提供了从数据源(如文件、网络连接、内存等)读取字节数据的基本接口。本文将详细解析InputStream
的核心概念、常用方法、设计模式及源码实现。
一、核心概念
1. 作用
InputStream
是Java IO体系中字节输入流的顶层抽象,定义了读取字节数据的标准方法,所有具体的字节输入流类(如FileInputStream
、ByteArrayInputStream
、SocketInputStream
等)都继承自它。
2. 数据源类型
InputStream
的数据源可以是:
- 文件(
FileInputStream
) - 内存(
ByteArrayInputStream
) - 网络连接(
Socket.getInputStream()
) - 管道(
PipedInputStream
) - 其他流(如
BufferedInputStream
包装其他流)
二、常用方法
1. 核心读取方法
// 读取下一个字节(0-255),返回-1表示流结束
public abstract int read() throws IOException;
// 读取数据到字节数组b,返回实际读取的字节数
public int read(byte b[]) throws IOException;
// 读取len个字节到数组b的off位置开始,返回实际读取的字节数
public int read(byte b[], int off, int len) throws IOException;
2. 其他重要方法
// 跳过n个字节,返回实际跳过的字节数
public long skip(long n) throws IOException;
// 返回可以读取的字节数估计值
public int available() throws IOException;
// 关闭流,释放资源
public void close() throws IOException;
// 标记当前位置,配合reset()使用
public synchronized void mark(int readlimit);
// 重置到上次mark()的位置
public synchronized void reset() throws IOException;
// 判断是否支持mark/reset功能
public boolean markSupported();
三、源码解析
1. 抽象方法 read()
public abstract int read() throws IOException;
- 作用:读取下一个字节数据,返回值为
0-255
的整数,若返回-1
表示流结束。 - 实现要求:所有子类必须实现此方法,它是
InputStream
最核心的方法。
2. 批量读取方法 read(byte[] b)
public int read(byte b[]) throws IOException {
return read(b, 0, b.length);
}
- 作用:将数据读取到字节数组
b
中,调用了read(b, 0, b.length)
的重载方法。
3. 核心重载方法 read(byte[] b, int off, int len)
public int read(byte b[], int off, int len) throws IOException {
if (b == null) {
throw new NullPointerException();
} else if (off < 0 || len < 0 || len > b.length - off) {
throw new IndexOutOfBoundsException();
} else if (len == 0) {
return 0;
}
int c = read(); // 调用抽象方法read()
if (c == -1) {
return -1;
}
b[off] = (byte)c;
int i = 1;
try {
for (; i < len ; i++) {
c = read(); // 循环调用read()
if (c == -1) {
break;
}
b[off + i] = (byte)c;
}
} catch (IOException ee) {
// 异常处理
}
return i;
}
- 实现逻辑:
- 检查参数合法性。
- 调用
read()
读取第一个字节。 - 循环调用
read()
将数据填充到数组b
中,直到读取len
个字节或流结束。
- 性能问题:直接使用此方法效率较低,因为每次只读取一个字节,建议使用
BufferedInputStream
包装以提高性能。
4. skip()
方法实现
public long skip(long n) throws IOException {
long remaining = n;
int nr;
if (n <= 0) {
return 0;
}
int size = (int)Math.min(MAX_SKIP_BUFFER_SIZE, remaining);
byte[] skipBuffer = new byte[size];
while (remaining > 0) {
nr = read(skipBuffer, 0, (int)Math.min(size, remaining));
if (nr < 0) {
break;
}
remaining -= nr;
}
return n - remaining;
}
- 实现逻辑:
- 创建一个临时缓冲区
skipBuffer
。 - 循环读取数据到缓冲区,直到跳过指定的字节数
n
。
- 创建一个临时缓冲区
5. available()
方法
public int available() throws IOException {
return 0;
}
- 默认实现:返回
0
,表示无法提前知道可用字节数。 - 子类重写:如
FileInputStream
会返回文件剩余未读的字节数。
6. close()
方法
public void close() throws IOException {}
- 默认实现:空方法,因为抽象类无需释放资源。
- 子类重写:如
FileInputStream
会关闭文件句柄,SocketInputStream
会关闭网络连接。
四、设计模式
1. 模板方法模式
InputStream
定义了核心方法的骨架(如read(byte[])
调用read()
),具体实现由子类完成,符合模板方法模式。
2. 装饰器模式
FilterInputStream
及其子类(如BufferedInputStream
、DataInputStream
)通过包装其他InputStream
实现功能增强,符合装饰器模式。
五、常见子类
1. FileInputStream
从文件读取数据,直接关联底层文件描述符。
2. ByteArrayInputStream
从内存字节数组读取数据,无需进行IO操作,适用于测试和数据处理。
3. BufferedInputStream
通过缓冲区提高读取性能,减少实际IO次数。
4. DataInputStream
用于读取基本数据类型(如int
、double
),需配合DataOutputStream
使用。
5. ObjectInputStream
用于反序列化对象,实现Java对象的持久化。
六、使用示例
1. 读取文件内容
try (InputStream is = new FileInputStream("test.txt")) {
byte[] buffer = new byte[1024];
int bytesRead;
while ((bytesRead = is.read(buffer)) != -1) {
System.out.write(buffer, 0, bytesRead);
}
} catch (IOException e) {
e.printStackTrace();
}
2. 使用缓冲流提高性能
try (InputStream is = new FileInputStream("test.txt");
BufferedInputStream bis = new BufferedInputStream(is)) {
int data;
while ((data = bis.read()) != -1) {
// 处理数据
}
} catch (IOException e) {
e.printStackTrace();
}
七、注意事项
- 资源管理:
InputStream
使用完毕后必须调用close()
关闭,建议使用try-with-resources
语句自动关闭。 - 性能优化:直接使用
read()
单字节读取效率低,建议使用BufferedInputStream
或批量读取方法。 - 阻塞特性:传统IO是阻塞的,若需非阻塞IO,可使用
java.nio
包中的FileChannel
或SocketChannel
。
总结
InputStream
作为Java IO体系的基础组件,定义了字节输入流的标准接口,通过抽象方法和模板模式为子类提供了统一的操作框架。理解其设计思想和源码实现,有助于高效使用各种具体流类,并在需要时自定义扩展。