TeeInputStream分支流
一、TeeInputStream 是什么?
TeeInputStream 是 Apache Commons IO 提供的一个 InputStream 装饰器类。
它的作用类似于 Linux 命令 tee:从原始输入流读取数据时,同时将读取到的每个字节复制一份写入到一个 OutputStream 中。
- 分流:原始流 -> 程序读取(主用途),同时自动 -> 写入分支
OutputStream(副用途)。 - 数据只读取一次:原始流的字节仅从底层真正读取一次,却同时流向两个目标。
二、基本用法(配合你的 MinIO 上传场景)
你可以在一次流处理中,既计算文件的 SHA-256 哈希,又把文件内容缓存下来供后续上传使用。
依赖
xml
<dependency>
<groupId>commons-io</groupId>
<artifactId>commons-io</artifactId>
<version>2.17.0</version>
</dependency>代码示例
java
import org.apache.commons.io.input.TeeInputStream;
import java.io.*;
import java.security.MessageDigest;
// 分支输出流:用于缓存文件内容
ByteArrayOutputStream cachedStream = new ByteArrayOutputStream();
MessageDigest digest = MessageDigest.getInstance("SHA-256");
try (InputStream originalStream = file.getInputStream();
// 包装原始流,所有从 teeStream 读取的数据都会同时写入 cachedStream
TeeInputStream teeStream = new TeeInputStream(originalStream, cachedStream, true)) {
// 读取 teeStream 计算哈希
byte[] buffer = new byte[8192];
int bytesRead;
while ((bytesRead = teeStream.read(buffer)) != -1) {
digest.update(buffer, 0, bytesRead);
}
String hash = bytesToHex(digest.digest());
// 检查文件是否已存在...
if (需要上传) {
byte[] fileBytes = cachedStream.toByteArray(); // 缓存数据
try (ByteArrayInputStream uploadStream = new ByteArrayInputStream(fileBytes)) {
minioClient.putObject(
PutObjectArgs.builder()
.stream(uploadStream, fileBytes.length, -1)
.contentType(file.getContentType())
.bucket(bucketName)
.object(objectName)
.build()
);
}
}
} // 原始流、TeeInputStream、cachedStream 都会在此关闭(cachedStream 由 TeeInputStream 构造参数控制)构造参数说明
java
TeeInputStream(InputStream input, OutputStream branch, boolean closeBranch)input:原始输入流。branch:分支输出流,每读取一个字节都会写入。closeBranch:当TeeInputStream关闭时,是否自动关闭branch输出流。一般设为true配合 try-with-resources 自动关闭。
三、优点
| 优点 | 说明 |
|---|---|
| 避免重复读取原始流 | 数据只从物理层读取一次,彻底解决“流只能消费一次”的痛点。 |
| 内存/磁盘使用可控 | 分支输出流可以根据需要选择 ByteArrayOutputStream(内存)或 FileOutputStream(磁盘),实现灵活的数据保留方式。 |
| 代码简洁 | 不需要手动将流先读为 byte[] 再分两次处理,逻辑集中在一段循环内。 |
| 保持数据一致性 | 由于是同时分流,哈希计算和缓存的数据来自同一次读取,不存在前后不一致的风险。 |
四、缺点
| 缺点 | 说明 |
|---|---|
| 仍然需要缓存全量数据 | 分支输出流依然要在内存(或临时文件)中保存文件完整内容,否则上传时拿不到数据。对于大文件仍会导致内存压力(若用内存缓存)。 |
| 额外 I/O 复制开销 | 每次读取都会同步写入分支流,无形中增加了 CPU 和内存的复制成本(不过通常可忽略)。 |
| 依赖第三方库 | 必须引入 Apache Commons IO,增加依赖复杂度。 |
| 可能掩盖分支流异常 | 如果分支输出流写入失败(如磁盘满),TeeInputStream 默认会抛出异常并终止读取,有时这种“连带失败”不是期望的行为。 |
| 不适合不需要完整缓存的场景 | 如果后续操作并不需要完整的文件内容,只是计算个哈希之类的,引入 TeeInputStream 并缓存全量数据反而浪费资源。 |
五、适用场景与不适用场景
适合使用 TeeInputStream 的场景
- 边下载/读取边哈希,同时需要持久化或上传(比如代理下载并计算文件完整性)。
- 需要在一次流读取中同时做多种处理(例如读取 HTTP 响应体,一边解析 JSON,一边写入本地缓存)。
- 你确定文件大小不大,使用内存缓存完全没问题,而且希望代码逻辑集中在一处。
不适合使用的场景
- 文件可能非常大:应优先考虑临时文件方案,避免将全部内容读入内存。
- 仅需计算哈希,不需复用数据:直接用
DigestInputStream更轻量,无需复制全部内容。 - 希望保持最小依赖:不想引入 Commons IO 的项目,用先读 byte[] 的方案更简单。
六、与其他方案的对比总结
| 方案 | 优点 | 缺点 |
|---|---|---|
| 先读为 byte[] | 代码最简单、无额外依赖、流只读一次 | 大文件内存占用高 |
| TeeInputStream + ByteArrayOutputStream | 一次读取同时完成哈希与缓存,代码集中 | 额外依赖,仍然要在内存中缓存全量文件 |
| TeeInputStream + 临时文件 | 兼顾大文件、一次读取 | 磁盘 I/O,代码稍复杂,需手动清理临时文件 |
| DigestInputStream + 先存临时文件 | 标准库支持,无第三方依赖,适合大文件 | 需要两次 I/O(或一次读取后 seek) |
如果你已经使用了 Apache Commons IO,并且文件大小在可控范围内,TeeInputStream 是一个优雅的一体化方案。否则直接用 byte[] 缓存会更简单直接。