字节流
概述
字节流以字节(byte)为单位读写数据,是 Java IO 中最底层的流式抽象。InputStream 与 OutputStream 是字节流的两个抽象基类:前者表示输入源(如文件、网络、内存),后者表示输出目标。字节流适用于任意二进制数据(如图片、音频、视频、压缩包)以及按字节处理的文本;若主要处理文本且需要按字符/行处理,应使用 字符流(Reader/Writer)。
字节流体系概览
| 类型 | 常用实现类 | 说明 |
|---|---|---|
| InputStream | FileInputStream、ByteArrayInputStream、BufferedInputStream | 从文件、字节数组、缓冲等读取字节 |
| OutputStream | FileOutputStream、ByteArrayOutputStream、BufferedOutputStream | 向文件、字节数组、缓冲等写入字节 |
FileInputStream / FileOutputStream 直接绑定文件,是文件读写最常用的字节流实现。使用前需通过 File 类 或路径字符串指定文件位置。
基本用法
InputStream 核心方法
| 方法 | 说明 |
|---|---|
int read() | 读取一个字节,返回 0–255;若已到末尾返回 -1 |
int read(byte[] b) | 将字节读入数组 b,返回实际读取的字节数;末尾返回 -1 |
int read(byte[] b, int off, int len) | 将最多 len 个字节读入 b,从 off 开始存放;末尾返回 -1 |
void close() | 关闭流并释放资源,必须调用 |
read(byte[] b) 会尽可能把 b.length 个字节读入数组,返回值可能小于数组长度(例如文件剩余字节不足时)。
OutputStream 核心方法
| 方法 | 说明 |
|---|---|
void write(int b) | 写出一个字节(低 8 位有效) |
void write(byte[] b) | 写出整个字节数组 |
void write(byte[] b, int off, int len) | 写出数组 b 中从 off 起的 len 个字节 |
void flush() | 刷新缓冲(部分实现类有缓冲时有效) |
void close() | 关闭流并释放资源,必须调用 |
说明
FileInputStream / FileOutputStream 的 read、write 会抛出受检异常 IOException,必须用 try-catch 捕获或在方法上 throws IOException。推荐用 try-with-resources 自动关闭流。详见 异常处理。
使用示例
示例 1:用 FileInputStream 读取文件(逐字节)
import java.io.FileInputStream;
import java.io.IOException;
public class ReadFileByByte {
public static void main(String[] args) {
// try-with-resources 确保流在结束时自动关闭,避免资源泄漏
try (FileInputStream in = new FileInputStream("data/hello.txt")) {
int b;
while ((b = in.read()) != -1) {
// 读到的是字节(0-255),若文件是文本可强转为 char 查看(需注意编码)
System.out.print((char) b);
}
} catch (IOException e) {
System.err.println("读取失败: " + e.getMessage());
}
}
}逐字节 read() 适合小文件或仅需「扫一遍」的场景;大文件更推荐按块读取(见示例 2)。
示例 2:按字节数组读取(推荐)
import java.io.FileInputStream;
import java.io.IOException;
public class ReadFileByBuffer {
public static void main(String[] args) {
byte[] buffer = new byte[8192]; // 8KB 一块,按块读减少系统调用次数
try (FileInputStream in = new FileInputStream("data/hello.txt")) {
int len;
while ((len = in.read(buffer)) != -1) {
// len 为本轮实际读到的字节数,可能小于 buffer.length
processBytes(buffer, 0, len);
}
} catch (IOException e) {
System.err.println("读取失败: " + e.getMessage());
}
}
private static void processBytes(byte[] b, int off, int len) {
// 例如:写入另一流、解析协议、或按需转成字符串(需指定编码)
System.out.write(b, off, len);
}
}提示
大文件读写应使用 read(byte[] b) / write(byte[] b) 配合合理大小的数组(如 4KB–64KB),避免逐字节导致性能差。需要更高性能时可使用 缓冲流。
示例 3:用 FileOutputStream 写入文件
import java.io.FileOutputStream;
import java.io.IOException;
public class WriteFileDemo {
public static void main(String[] args) {
// 第二个参数 true 表示追加,默认 false 会覆盖原文件
try (FileOutputStream out = new FileOutputStream("output/result.bin", false)) {
byte[] data = { 0x48, 0x65, 0x6C, 0x6C, 0x6F }; // "Hello" 的 ASCII
out.write(data);
out.write('\n');
// FileOutputStream 一般无需 flush,close 时会写入
} catch (IOException e) {
System.err.println("写入失败: " + e.getMessage());
}
}
}示例 4:文件复制(字节流 + try-with-resources)
import java.io.FileInputStream;
import java.io.FileOutputStream;
import java.io.IOException;
public class CopyFileDemo {
public static void copy(String src, String dest) throws IOException {
try (FileInputStream in = new FileInputStream(src);
FileOutputStream out = new FileOutputStream(dest)) {
byte[] buffer = new byte[8192];
int len;
while ((len = in.read(buffer)) != -1) {
out.write(buffer, 0, len); // 只写出本轮读到的 len 个字节
}
}
}
public static void main(String[] args) {
try {
copy("data/source.dat", "output/copy.dat");
System.out.println("复制完成");
} catch (IOException e) {
System.err.println("复制失败: " + e.getMessage());
}
}
}注意
read(buffer) 返回的 len 可能小于 buffer.length,写盘时必须用 write(buffer, 0, len),若写成 write(buffer) 会把数组中未读入的旧数据也写出,造成错误。
注意事项
注意
字节流操作会抛出受检异常 IOException(如文件不存在、无权限、磁盘满)。必须用 try-catch 处理或在方法上 throws IOException,否则无法通过编译。
注意
必须关闭流。未关闭的流会占用文件句柄和系统资源,导致「打开文件过多」等错误。推荐使用 try-with-resources,在 try 块结束时自动调用 close()。
| 易错点 | 说明 |
|---|---|
| read() 返回值 | read() 返回 int:0–255 表示一个字节,-1 表示结束;不要当作 byte 直接比较。 |
| write(buffer) 与 len | 按块读写时,写出时要用 write(buffer, 0, len),不要写出整个数组。 |
| 文本与编码 | 字节流不处理字符编码;处理文本文件且需按字符/行操作时,应使用 字符流。 |
| 覆盖与追加 | FileOutputStream(path) 会覆盖原文件;FileOutputStream(path, true) 为追加模式。 |
相关链接
- File 类与文件操作 — 路径与文件元数据,字节流读写前常需配合使用
- 字符流 — Reader/Writer,按字符处理文本
- 缓冲流与包装流 — BufferedInputStream/BufferedOutputStream,提升读写性能
- 异常处理 — try-catch、throws、try-with-resources
- Oracle Java 教程 - I/O Streams