字节数组流:内存中的 IO
磁盘 IO 慢,网络 IO 更慢。但在很多场景下,我们根本不需要跟磁盘或网络打交道——数据就在内存里。
比如:测试时模拟输入、拼接二进制数据、封装输出结果... 这些场景,ByteArrayInputStream 和 ByteArrayOutputStream 是最佳选择。
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 级以上)
- 需要频繁读写
- 对性能要求极高
