OutputStream 本身是输出流,作用是往外写数据,InputStream 是输入流,作用是读取数据,Java 原生 API不能直接把 OutputStream 对象强转成 InputStream,本质是两个方向相反的流,转换核心思路就是先把 OutputStream 里面写入的数据,保存到中间载体,再基于这个中间载体创建对应的 InputStream,最常用中间载体是 ByteArrayOutputStream,也就是字节数组输出流,写完数据之后调用 toByteArray 拿到字节数组,再用 ByteArrayInputStream 包装这个字节数组得到 InputStream,大数据场景也可以使用 PipedOutputStream 管道流搭配 PipedInputStream,实现边写边读,不需要一次性把全部数据加载到内存,这个知识点是 Java IO 开发高频面试题,很多新手会犯错误,尝试 (InputStream) outputStream 强转,运行直接报类型转换异常,一定要理解 IO 流的方向模型,输出流是写,输入流是读,二者只是继承自 OutputStream 和 InputStream 顶层抽象类,不存在互相直接转换的内置方法。
两种主流实现方案
方案一 ByteArrayOutputStream 内存缓冲方案(小数据首选)
原理,数据全部写入 ByteArrayOutputStream,全部写完之后提取 byte 数组,新建 ByteArrayInputStream
优点,代码简单,同步执行,稳定可靠
缺点,全部数据驻留内存,文件很大的时候内存占用高,容易 OOM
方案二 PipedOutputStream 管道流方案(大数据边写边读)
原理,PipedOutputStream 和 PipedInputStream 互相连接,一边线程写 OutputStream,另一边线程读取 InputStream,流式传输,不需要一次性存全部字节
优点,内存占用低,适合大文件
缺点,必须多线程,读写速度不匹配会阻塞,编码复杂度更高
核心代码示例 方案一
// 创建字节输出流
ByteArrayOutputStream baos = new ByteArrayOutputStream();
// 往OutputStream写入业务数据
baos.write("test data".getBytes());
// 转成InputStream
InputStream inputStream = new ByteArrayInputStream(baos.toByteArray());
核心代码示例 方案二
PipedInputStream pis = new PipedInputStream();
PipedOutputStream pos = new PipedOutputStream(pis);
// 单独线程执行写OutputStream
new Thread(() -> {
try {
pos.write("piped data".getBytes());
pos.close();
} catch (Exception e) {
e.printStackTrace();
}
}).start();
// 当前线程直接读取pis也就是InputStream
两种方案对比表格
| 对比项 | ByteArrayOutputStream | PipedOutputStream 管道流 |
|---|---|---|
| 是否需要多线程 | 不需要 | 必须多线程 |
| 内存占用 | 全部数据存入内存 | 流式,内存占用低 |
| 适用场景 | 小文件 小字节数据 | 大文件 边写边读 |
| 风险 | 大数据 OOM | 读写速度差造成阻塞死锁 |
| 代码复杂度 | 简单 | 较高 |
转换执行步骤
第一步 明确业务数据大小,如果是小数据,优先选用 ByteArrayOutputStream 方案
第二步 实例化 OutputStream,执行 write 写入全部业务数据
第三步 写入完成,关闭 OutputStream,释放流资源
第四步 取出中间载体数据,实例化 InputStream
第五步 使用 InputStream 完成读取业务逻辑
第六步 读取结束,关闭 InputStream,注意 IO 流需要捕获异常,推荐 try with resources 自动关闭
易错重点
1 禁止强转,OutputStream 和 InputStream 是平级抽象类,不能强制类型转换
2 使用管道流的时候,如果写线程和读线程在同一个线程,会发生死锁阻塞
3 toByteArray 之后,如果原始 baos 数据非常大,要评估 JVM 堆内存,避免 OOM
4 流关闭顺序,管道流写完之后一定要 close,否则读取端会一直阻塞等待数据
5 除了 JDK 原生方案,第三方库如 commons io 也提供相关工具类,但底层原理依然是中间缓冲思路
【常见问题】
问题 1 outputstream 转 inputstream 能不能直接强转
回答 1 outputstream 转 inputstream 不能直接强转,OutputStream 是写流,InputStream 是读流,属于两个独立抽象类,强转会抛出 ClassCastException,必须借助中间载体转换
问题 2 outputstream 转 inputstream 大数据推荐哪种方式
回答 1 outputstream 转 inputstream 大数据场景推荐 PipedOutputStream 管道流方案,边写边读,不用把全部数据加载内存,避免 ByteArrayOutputStream 方案大数据 OOM 问题
问题 3 outputstream 转 inputstream 使用 ByteArrayOutputStream 有什么缺点
回答 3 outputstream 转 inputstream 使用 ByteArrayOutputStream,缺点是所有写入的数据都会保存在内存字节数组,数据量很大的时候会占用大量堆内存,有 OOM 风险,不适合超大文件
问题 4 outputstream 转 inputstream 管道流为什么要开多线程
回答 4 outputstream 转 inputstream 管道流必须多线程,如果写和读放在同一个线程,写操作会阻塞,读取代码永远执行不到,造成线程死锁,所以需要单独线程执行 OutputStream 写入
【免责申明】
本文内容仅针对 Java 中 outputstream 转 inputstream 做技术原理与代码示例讲解,不同 JDK 版本以及框架封装实现会有差异,生产环境请结合业务评估内存与线程风险,代码仅作参考,不保证适配全部业务场景。


