1. 概述
本文将系统介绍如何将 InputStream
转换为 byte[]
和 ByteBuffer
。我们会先使用原生 Java 实现,再借助 Guava 和 Apache Commons IO 提供的工具类来简化操作。
这类转换在实际开发中非常常见,比如处理文件上传、网络请求体、序列化对象等场景。掌握多种实现方式,能让你在不同项目环境下灵活应对,避免踩坑。
本文属于 Baeldung “Java 回归基础” 系列的一部分。
2. 转换为 Byte 数组
byte[]
的优势在于支持随机访问,每个元素对应一个字节(8位),适合做底层数据操作,比如解析二进制协议、加密解密等。
我们将从三种方式展开:原生 Java、Guava、Commons IO。
2.1 使用原生 Java
✅ 固定大小流的处理
如果能确定输入流的大小(如 ByteArrayInputStream
),可以直接预分配数组:
@Test
public void givenUsingPlainJavaOnFixedSizeStream_whenConvertingAnInputStreamToAByteArray_thenCorrect()
throws IOException {
InputStream is = new ByteArrayInputStream(new byte[] { 0, 1, 2 });
byte[] targetArray = new byte[is.available()];
is.read(targetArray);
}
⚠️ 注意:available()
不一定准确,仅对某些实现(如内存流)可靠,不要用于网络或文件流判断总长度。
✅ 未知大小流的通用处理
对于大小未知的流(如文件、网络流),推荐使用 ByteArrayOutputStream
动态扩容:
@Test
public void givenUsingPlainJavaOnUnknownSizeStream_whenConvertingAnInputStreamToAByteArray_thenCorrect()
throws IOException {
InputStream is = new ByteArrayInputStream(new byte[] { 0, 1, 2, 3, 4, 5, 6 });
ByteArrayOutputStream buffer = new ByteArrayOutputStream();
int nRead;
byte[] data = new byte[4]; // 缓冲区大小可根据需要调整
while ((nRead = is.read(data, 0, data.length)) != -1) {
buffer.write(data, 0, nRead);
}
buffer.flush();
byte[] targetArray = buffer.toByteArray();
}
这是 Java 8 及之前最经典、最稳妥的写法,简单粗暴但有效。
✅ Java 9+ 的新方法
Java 9 引入了两个新方法,让代码更简洁:
readNBytes(byte[], offset, len)
:尽可能读取指定长度的字节,直到流结束。readAllBytes()
:一次性读取所有剩余字节。
@Test
public void givenUsingPlainJava9OnUnknownSizeStream_whenConvertingAnInputStreamToAByteArray_thenCorrect()
throws IOException {
InputStream is = new ByteArrayInputStream(new byte[] { 0, 1, 2, 3, 4, 5, 6 });
ByteArrayOutputStream buffer = new ByteArrayOutputStream();
byte[] data = new byte[4];
int nRead;
while ((nRead = is.readNBytes(data, 0, data.length)) != 0) {
buffer.write(data, 0, nRead);
}
buffer.flush();
byte[] targetArray = buffer.toByteArray();
}
对比 read()
和 readNBytes()
:
方法 | 行为 | 返回值 |
---|---|---|
read(...) |
最多读 len 字节,可能少于 len |
读取字节数;流结束返回 -1 |
readNBytes(...) |
尽可能读满 len 字节,阻塞直到读完或流结束 |
实际读取字节数(>=0) |
更进一步,直接读全部:
@Test
public void givenUsingPlainJava9_whenConvertingAnInputStreamToAByteArray_thenCorrect()
throws IOException {
InputStream is = new ByteArrayInputStream(new byte[] { 0, 1, 2 });
byte[] data = is.readAllBytes();
}
✅ 推荐:Java 9+ 环境下优先使用 readAllBytes()
,简洁安全。
2.2 使用 Guava
Guava 提供了 ByteStreams.toByteArray()
,一行搞定:
@Test
public void givenUsingGuava_whenConvertingAnInputStreamToAByteArray_thenCorrect()
throws IOException {
InputStream initialStream = ByteSource.wrap(new byte[] { 0, 1, 2 }).openStream();
byte[] targetArray = ByteStreams.toByteArray(initialStream);
}
✅ 优点:
- 无需关心流大小
- 内部自动缓冲,性能好
- API 简洁
⚠️ 缺点:引入 Guava 依赖,适合已使用 Guava 的项目。
2.3 使用 Apache Commons IO
Commons IO 的 IOUtils.toByteArray()
同样简洁:
@Test
public void givenUsingCommonsIO_whenConvertingAnInputStreamToAByteArray_thenCorrect()
throws IOException {
ByteArrayInputStream initialStream = new ByteArrayInputStream(new byte[] { 0, 1, 2 });
byte[] targetArray = IOUtils.toByteArray(initialStream);
}
✅ 关键点:IOUtils.toByteArray()
内部已经做了缓冲处理,所以你不需要再包装 BufferedInputStream
,直接传原始流即可。
📌 小贴士:Commons IO 在传统企业项目中使用广泛,兼容性好,依赖轻量。
3. 转换为 ByteBuffer
ByteBuffer
是 NIO 的核心类之一,适用于需要直接内存操作、零拷贝、通道(Channel)交互等高性能场景。
比如:网络通信(Netty)、文件映射、Direct Buffer 操作等。
3.1 使用原生 Java
如果已知数据大小,可以预先分配 ByteBuffer
:
@Test
public void givenUsingCoreClasses_whenByteArrayInputStreamToAByteBuffer_thenLengthMustMatch()
throws IOException {
byte[] input = new byte[] { 0, 1, 2 };
InputStream initialStream = new ByteArrayInputStream(input);
ByteBuffer byteBuffer = ByteBuffer.allocate(3);
while (initialStream.available() > 0) {
byteBuffer.put((byte) initialStream.read());
}
assertEquals(byteBuffer.position(), input.length);
}
⚠️ 注意:read()
返回 int
,需强转为 byte
。循环条件使用 available()
仅适用于内存流。
更通用的方式是结合 readNBytes()
:
// Java 9+
byte[] bytes = is.readAllBytes();
ByteBuffer buffer = ByteBuffer.wrap(bytes);
3.2 使用 Guava
Guava 没有直接返回 ByteBuffer
的方法,但可以结合 toByteArray()
使用:
@Test
public void givenUsingGuava__whenByteArrayInputStreamToAByteBuffer_thenLengthMustMatch()
throws IOException {
InputStream initialStream = ByteSource
.wrap(new byte[] { 0, 1, 2 })
.openStream();
byte[] targetArray = ByteStreams.toByteArray(initialStream);
ByteBuffer bufferByte = ByteBuffer.wrap(targetArray);
while (bufferByte.hasRemaining()) {
bufferByte.get();
}
assertEquals(bufferByte.position(), targetArray.length);
}
📌 解释:hasRemaining()
判断是否还有可读字节,get()
移动 position。如果不读,position 仍为 0,断言会失败。
3.3 使用 Apache Commons IO
Commons IO 提供了 IOUtils.readFully()
,可直接读入 ByteBuffer
:
@Test
public void givenUsingCommonsIo_whenByteArrayInputStreamToAByteBuffer_thenLengthMustMatch()
throws IOException {
byte[] input = new byte[] { 0, 1, 2 };
InputStream initialStream = new ByteArrayInputStream(input);
ByteBuffer byteBuffer = ByteBuffer.allocate(3);
ReadableByteChannel channel = Channels.newChannel(initialStream);
IOUtils.readFully(channel, byteBuffer);
assertEquals(byteBuffer.position(), input.length);
}
✅ readFully()
保证填满 buffer,否则抛出 EOFException
,适合已知长度的场景。
4. 总结
方式 | 适用场景 | 建议 |
---|---|---|
InputStream.readAllBytes() (Java 9+) |
✅ 推荐新项目使用 | 简洁、安全、无需依赖 |
IOUtils.toByteArray() (Commons IO) |
✅ 传统项目、Spring 生态 | 依赖常见,稳定性高 |
ByteStreams.toByteArray() (Guava) |
✅ 已引入 Guava 的项目 | 功能强大,API 优雅 |
原生 ByteArrayOutputStream 循环读取 |
✅ Java 8- 环境兼容 | 经典写法,理解原理必备 |
📌 最佳实践建议:
- 新项目(Java 9+):直接用
InputStream::readAllBytes
- 老项目或受限环境:优先选 Commons IO,其次 Guava
- 性能敏感场景:注意缓冲区大小设置,避免频繁扩容
- 大文件处理:⚠️ 不要一次性转为内存数组,应采用流式处理
所有示例代码均可在 GitHub 项目 中找到。