Skip to content

字节数组流:内存中的 IO

磁盘 IO 慢,网络 IO 更慢。但在很多场景下,我们根本不需要跟磁盘或网络打交道——数据就在内存里。

比如:测试时模拟输入、拼接二进制数据、封装输出结果... 这些场景,ByteArrayInputStreamByteArrayOutputStream 是最佳选择。

ByteArrayInputStream:从数组读

数据已经在内存里了,直接拿来读:

java
byte[] data = "Hello World".getBytes();
ByteArrayInputStream bais = new ByteArrayInputStream(data);

int b;
while ((b = bais.read()) != -1) {
    System.out.print((char) b);
}

// 或者批量读
byte[] buffer = new byte[1024];
int len = bais.read(buffer);

三个关键特点

  • 无 IO 开销:数据来源是内存中的数组,不触发任何系统调用
  • 不需要关闭close() 是空操作,不释放任何资源
  • 可以重复读:标记后可以 reset 回退
java
// mark/reset 支持
ByteArrayInputStream bais = new ByteArrayInputStream("test data".getBytes());
bais.mark(5);
bais.read(new byte[3]);  // 读 3 字节
bais.reset();             // 回到标记位置
bais.read();              // 重新读第一个字节

ByteArrayOutputStream:写到数组

把数据写到内存中的数组:

java
ByteArrayOutputStream baos = new ByteArrayOutputStream();
baos.write("Hello".getBytes());
baos.write(" ".getBytes());
baos.write("World".getBytes());

// 获取结果
byte[] result = baos.toByteArray();  // 转成 byte[]
String str = baos.toString();        // 转成 String(用系统默认编码)

自动扩容:内部是动态数组,写超过容量时自动扩容(类似 ArrayList):

java
ByteArrayOutputStream baos = new ByteArrayOutputStream();
// 默认容量 32,写超过 32 字节会自动扩容(翻倍)
for (int i = 0; i < 100; i++) {
    baos.write(i);
}
byte[] result = baos.toByteArray();  // 容量自动扩展到 128

// 如果知道大概大小,指定初始容量避免扩容
ByteArrayOutputStream baos = new ByteArrayOutputStream(4096);  // 初始 4KB

实战场景

场景 1:内存中拼接二进制数据

把多个数据源合并成一个字节数组:

java
// 网络协议:把多个字段拼成一个数据包
ByteArrayOutputStream baos = new ByteArrayOutputStream();
DataOutputStream dos = new DataOutputStream(baos);

dos.writeInt(header);           // 4 字节
dos.writeUTF(content);          // 2+N 字节
dos.writeLong(timestamp);       // 8 字节

byte[] packet = baos.toByteArray();
// 发送 packet 到网络
socket.getOutputStream().write(packet);

场景 2:封装输出结果

把要发送的内容先写到内存,最后一次性发送:

java
// HTTP 响应
ByteArrayOutputStream baos = new ByteArrayOutputStream();
PrintWriter pw = new PrintWriter(baos);
pw.println("HTTP/1.1 200 OK");
pw.println("Content-Type: text/html");
pw.println();
pw.println("<h1>Hello</h1>");
pw.flush();

// 一次性发送
byte[] httpResponse = baos.toByteArray();
socket.getOutputStream().write(httpResponse);

场景 3:单元测试

模拟输入、捕获输出:

java
// 测试某个读取方法
byte[] testData = "test input data".getBytes();
InputStream input = new ByteArrayInputStream(testData);

// 测试输出
ByteArrayOutputStream output = new ByteArrayOutputStream();
System.setOut(new PrintStream(output));

// 执行被测代码
someMethodThatWritesToStdout(input);

// 验证输出
String result = output.toString();
assert result.contains("expected");

场景 4:Base64 编解码

java
// 解码
byte[] decoded = Base64.getDecoder().decode(encodedString);

// 编码
ByteArrayOutputStream baos = new ByteArrayOutputStream();
baos.write(content.getBytes());
String encoded = Base64.getEncoder().encodeToString(baos.toByteArray());

性能特点

特点说明
无 IO 开销完全在内存中操作,不涉及系统调用
无需关闭close() 是空操作,不释放任何资源
自动扩容ByteArrayOutputStream 会自动扩展容量
可重复读ByteArrayInputStream 支持 mark/reset

注意事项

内存占用问题

java
// ❌ 不要用 ByteArrayOutputStream 处理超大数据
ByteArrayOutputStream baos = new ByteArrayOutputStream();
// 如果写入 1GB 数据,会占用 1GB+ 堆内存
// 大文件处理用 FileInputStream/FileOutputStream

// ✅ 大文件应该直接写文件
try (BufferedOutputStream bos = new BufferedOutputStream(
        new FileOutputStream("large.dat"))) {
    // 直接写文件,不占用大量内存
}

编码问题

toString() 默认使用系统编码,如果要指定编码:

java
// ❌ 默认编码可能不一致
String str = baos.toString();

// ✅ 指定 UTF-8
String str = baos.toString(StandardCharsets.UTF_8);

什么时候用

  • 测试时模拟输入输出
  • 需要在内存中拼接二进制数据
  • 需要把数据封装后再发送
  • 不确定最终大小,需要动态扩容

什么时候不用

  • 处理大文件(MB 级以上)
  • 需要频繁读写
  • 对性能要求极高

基于 VitePress 构建