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 项目 中找到。


原始标题:Java InputStream to Byte Array and ByteBuffer