1. 概述

处理流是编程中常见的任务,特别是在进行输入输出操作时。有时,我们需要将OutputStream转换为字节数组,这在诸如网络编程、文件处理或数据操纵等场景中非常有用。

本教程将探讨两种方法来实现这个转换。

2. 使用Apache Commons IO库的FileUtils

Apache Commons IO库提供了FileUtils类,其中包含readFileToByteArray()方法,可以间接将FileOutputStream转换为字节数组。这是通过首先写入文件,然后从文件系统读取结果字节实现的。

要在项目中使用此库,我们需要先在项目中添加以下 Commons IO 的 依赖项

<dependency>
    <groupId>commons-io</groupId>
    <artifactId>commons-io</artifactId>
    <version>2.11.0</version>
</dependency>

让我们看一个简单的示例来实现这个功能:

@Test
public void givenFileOutputStream_whenUsingFileUtilsToReadTheFile_thenReturnByteArray(@TempDir Path tempDir) throws IOException {
    String data = "Welcome to Baeldung!";
    String fileName = "file.txt";
    Path filePath = tempDir.resolve(fileName);

    try (FileOutputStream outputStream = new FileOutputStream(filePath.toFile())) {
        outputStream.write(data.getBytes(StandardCharsets.UTF_8));
    }

    byte[] writtenData = FileUtils.readFileToByteArray(filePath.toFile());
    String result = new String(writtenData, StandardCharsets.UTF_8);
    assertEquals(data, result);
}

在上述测试方法中,我们初始化了一个字符串data和一个filePath。接着,我们使用FileOutputStream将字符串的字节表示写入文件。随后,我们使用FileUtils.readFileToByteArray()方法有效地将文件内容转换为字节数组。

最后,将字节数组转换回字符串,并通过断言确认原始的dataresult相等。

值得注意的是,这种方法仅适用于FileOutputStream,因为只有在流关闭后,我们才能检查写入的文件。对于更通用的解决方案,适用于不同类型的OutputStream,下一节将介绍一种提供更广泛适用性的替代方法。

3. 使用自定义的DrainableOutputStream

另一种方法是创建一个自定义的DrainableOutputStream类,它扩展了FilterOutputStream。这个类会在将字节写入底层OutputStream的同时,在内存中保留一份副本,从而允许稍后转换为字节数组。

首先,让我们创建一个自定义的DrainableOutputStream类,它扩展了FilterOutputStream

public class DrainableOutputStream extends FilterOutputStream {
    private final ByteArrayOutputStream buffer;

    public DrainableOutputStream(OutputStream out) {
        super(out);
        this.buffer = new ByteArrayOutputStream();
    }

    @Override
    public void write(byte b[]) throws IOException {
        buffer.write(b);
        super.write(b);
    }

    public byte[] toByteArray() {
        return buffer.toByteArray();
    }
}

在上面的代码中,我们首先声明一个类DrainableOutputStream,它会包装给定的OutputStream。我们包括一个ByteArrayOutputStream缓冲区buffer来积累写入的字节,并重写write()方法以捕获字节。我们还实现了toByteArray()方法以提供访问捕获的字节的途径。

现在,让我们使用DrainableOutputStream

@Test
public void givenSystemOut_whenUsingDrainableOutputStream_thenReturnByteArray() throws IOException {
    String data = "Welcome to Baeldung!\n";

    DrainableOutputStream drainableOutputStream = new DrainableOutputStream(System.out);
    try (drainableOutputStream) {
        drainableOutputStream.write(data.getBytes(StandardCharsets.UTF_8));
    }

    byte[] writtenData = drainableOutputStream.toByteArray();
    String result = new String(writtenData, StandardCharsets.UTF_8);
    assertEquals(data, result);
}

在上述测试方法中,我们首先初始化一个想要写入OutputStream的字符串data然后,我们利用DrainableOutputStream来拦截这个过程,通过在实际写入到OutputStream之前捕获字节。随后,我们使用toByteArray()方法将累积的字节转换为字节数组。

接着,我们根据捕获的字节数组创建一个新的字符串result,并断言其与原始data相等。

请注意,一个全面的DrainableOutputStream实现还需要为除了展示的示例之外的其他写入方法提供类似的重写。

4. 考虑因素和限制

虽然前几节中介绍的方法为将OutputStream转换为字节数组提供了实用的途径,但有必要认识到这项任务相关的某些考虑因素和限制。

将任意OutputStream转换为字节数组通常不是一个直接的操作,因为在字节被写入之后,可能无法或不切实际地获取它们。

某些子类,如FileOutputStreamByteArrayOutputStream,有内置机制允许我们获取输出字节,例如内存缓冲区或已写入的文件。然而,如果没有这样的输出字节副本,我们可能需要考虑使用类似DrainableOutputStream的技术,在写入字节时进行复制。

5. 总结

总之,编程中有这样的情景:将OutputStream转换为Java中的字节数组是很有用的操作。我们已经看到了如何使用Apache Commons IO库的FileUtils.readFileToByteArray()方法读取FileOutputStream生成的文件,以及使用自定义的DrainableOutputStream来为特定的OutputStream获取写入字节的更通用方法。

如往常一样,配套的源代码可以在 GitHub 上找到。