Java IO 全解析:从基础流到序列化
Java 的 I/O(输入 / 输出)机制是程序与外部世界(文件、网络、控制台等)交互的核心,其设计围绕 “流(Stream)” 展开 —— 通过流的方式有序传输数据。本文将从 I/O 操作模式出发,详细解析 Java IO 体系的核心类、使用方法及最佳实践。
I/O 操作模式:五种基本类型
I/O 操作的本质是 “数据从设备到内核缓冲区,再从缓冲区到用户进程” 的过程。根据等待数据的方式不同,分为五种模式:
| 模式 | 核心特点 | 适用场景 |
|---|---|---|
| 阻塞 I/O | 进程发起请求后阻塞,直到数据复制完成才唤醒。 | 简单场景(如单线程读取小文件) |
| 非阻塞 I/O | 进程不阻塞,定期轮询缓冲区状态,数据就绪后再复制(复制时可能阻塞)。 | 需快速响应的场景 |
| I/O 复用 | 单进程监控多个 I/O 通道,数据就绪后通知进程处理(如 select/epoll)。 |
高并发网络编程(如 NIO) |
| 信号驱动 I/O | 进程不阻塞,数据就绪后内核通过信号通知,再处理复制。 | 实时性要求高的场景 |
| 异步 I/O | 进程发起请求后完全不阻塞,内核自动完成全流程,完成后通知进程。 | 高性能 I/O 场景(如磁盘操作) |
注:Java 传统 IO(
java.io)主要基于阻塞 I/O,而 NIO(java.nio)引入了 I/O 复用机制。
Java IO 核心体系:流的分类
Java IO 包(java.io)的类按功能可分为四大类,核心是 “字节流” 和 “字符流”:
| 类型 | 核心接口 / 类 | 处理数据类型 | 典型用途 |
|---|---|---|---|
| 字节流 | InputStream(输入)、OutputStream(输出) |
二进制数据(字节) | 图片、视频、压缩文件等 |
| 字符流 | Reader(输入)、Writer(输出) |
文本数据(字符) | 文本文件、配置文件等 |
| 磁盘操作 | File |
文件 / 目录元数据 | 创建 / 删除文件、获取路径 |
网络操作(java.net) |
Socket、ServerSocket |
网络字节流 | 客户端 / 服务器通信 |
字节流:处理二进制数据
字节流以字节(8 位)为单位传输数据,是所有 I/O 操作的基础。核心基类为抽象类 InputStream(输入)和 OutputStream(输出)。
字节输入流(InputStream)
所有字节输入流均继承自 InputStream,用于从数据源读取字节。
核心子类及功能
| 类 | 数据源 | 核心功能 | 构造器参数示例 |
|---|---|---|---|
ByteArrayInputStream |
内存字节数组 | 从内存缓冲区读取数据,无需磁盘 I/O。 | new byte[] {1,2,3} |
FileInputStream |
本地文件 | 从文件读取字节,是文件输入的基础类。 | "/data/test.bin" 或 new File(...) |
PipedInputStream |
管道输出流 | 与 PipedOutputStream 配合,实现线程间通信。 |
new PipedOutputStream() |
SequenceInputStream |
多个输入流 | 合并多个流为一个,按顺序读取。 | new InputStream[] {in1, in2} |
装饰器子类(FilterInputStream)
通过 “装饰器模式” 为基础流添加功能(如缓冲、数据类型转换):
| 类 | 增强功能 | 适用场景 |
|---|---|---|
BufferedInputStream |
增加缓冲区,减少磁盘 I/O 次数(默认 8KB)。 | 大文件读取(提升性能 10-100 倍) |
DataInputStream |
读取基本数据类型(int/double 等)。 |
与 DataOutputStream 配合读写结构化数据 |
PushbackInputStream |
支持 “回退” 已读取的字节(如解析协议)。 | 编译器、协议解析器 |
核心方法
读取数据:
1
2
3
4
5
6
7
8// 读取单个字节(返回值:0~255,-1 表示结束)
int read() throws IOException;
// 读取字节到数组 b 中,返回实际读取的字节数
int read(byte[] b) throws IOException;
// 读取字节到数组 b 的 off 位置,长度为 len
int read(byte[] b, int off, int len) throws IOException;控制流位置:
1
2
3
4
5
6
7
8// 跳过 n 个字节(返回实际跳过的字节数)
long skip(long n) throws IOException;
// 标记当前位置(readlimit:允许重复读取的最大字节数)
void mark(int readlimit);
// 重置到标记位置(需流支持 mark,如 BufferedInputStream)
void reset() throws IOException;关闭流:
void close()(释放资源,1.7+ 推荐用try-with-resources自动关闭)。
字节输出流(OutputStream)
所有字节输出流均继承自 OutputStream,用于将字节写入目标。
核心子类及功能
| 类 | 目标位置 | 核心功能 | 构造器参数示例 |
|---|---|---|---|
ByteArrayOutputStream |
内存字节数组 | 数据写入内存缓冲区,可通过 toByteArray() 获取。 |
初始容量(默认 32 字节) |
FileOutputStream |
本地文件 | 字节写入文件,是文件输出的基础类。 | "/data/test.bin" 或 new File(...) |
PipedOutputStream |
管道输入流 | 与 PipedInputStream 配合,线程间通信。 |
new PipedInputStream() |
装饰器子类(FilterOutputStream)
| 类 | 增强功能 | 适用场景 |
|---|---|---|
BufferedOutputStream |
缓冲区减少磁盘 I/O,flush() 强制写入。 |
大文件写入(提升性能) |
DataOutputStream |
写入基本数据类型(int/double 等)。 |
与 DataInputStream 配合保存结构化数据 |
PrintStream |
格式化输出(println() 等方法)。 |
标准输出(System.out)、日志输出 |
核心方法
写入数据:
1
2
3
4
5
6
7
8// 写入单个字节(仅取 int 的低 8 位)
void write(int b) throws IOException;
// 写入整个字节数组
void write(byte[] b) throws IOException;
// 写入数组 b 的 off 位置开始的 len 个字节
void write(byte[] b, int off, int len) throws IOException;刷新缓冲区:
void flush()(缓冲流需手动调用,确保数据写入目标)。关闭流:
void close()(关闭前自动调用flush())。
字符流:处理文本数据
字符流以字符(16 位 Unicode)为单位传输,自动处理字节与字符的编码转换(如 UTF-8、GBK),适合文本数据。核心基类为 Reader(输入)和 Writer(输出)。
核心类及转换流
- 转换流:字节流与字符流的桥梁(需指定编码):
InputStreamReader:将InputStream转为Reader(如new InputStreamReader(System.in, "UTF-8"))。OutputStreamWriter:将OutputStream转为Writer(如new OutputStreamWriter(out, "GBK"))。
- 缓冲字符流:
BufferedReader:提供readLine()方法(逐行读取文本),效率更高。BufferedWriter:提供newLine()方法(跨平台换行)。
- 文件字符流:简化版转换流(使用默认编码):
FileReader=new InputStreamReader(new FileInputStream(...))。FileWriter=new OutputStreamWriter(new FileOutputStream(...))。
示例:字符流读写文本文件
1 | // 读取文本文件(指定 UTF-8 编码) |
RandomAccessFile:随机访问文件
RandomAccessFile 是特殊的 I/O 类,不继承字节流 / 字符流,但实现了 DataInput 和 DataOutput 接口,支持随机读写文件(通过 seek() 定位位置)。
核心特性
- 模式:
"r"(只读)、"rw"(读写,文件不存在则创建)。 - 随机访问:
void seek(long pos)定位文件指针(单位:字节)。 - 读写基本类型:如
readInt()、writeDouble()等。
示例:修改文件中间内容
1 | try (RandomAccessFile raf = new RandomAccessFile("data.bin", "rw")) { |
适用场景:固定格式的记录文件(如日志、数据库文件),可直接定位到某条记录修改。
标准 IO:与系统交互
Java 提供三个标准流与系统交互,由 System 类管理:
| 流 | 类型 | 默认目标 / 源 | 用途 |
|---|---|---|---|
System.in |
InputStream |
键盘输入 | 读取用户输入 |
System.out |
PrintStream |
控制台输出 | 打印正常信息 |
System.err |
PrintStream |
控制台输出 | 打印错误信息 |
标准输入处理
System.in 是字节流,需转为字符流处理文本:
1 | // 读取控制台输入(输入 "end" 退出) |
重定向标准流
通过 System 类的静态方法重定向流(如将输出写入文件):
1 | try (PrintStream fileOut = new PrintStream(new FileOutputStream("log.txt"))) { |
序列化与反序列化
序列化是将对象转为字节序列(便于存储 / 传输),反序列化是将字节序列恢复为对象。
核心类与接口
Serializable:标记接口(无方法),只有实现该接口的类才能被序列化。ObjectOutputStream:序列化流,通过writeObject(Object)写入对象。ObjectInputStream:反序列化流,通过readObject()恢复对象。
关键规则
transient关键字:修饰的字段不参与序列化(如敏感信息)。- 静态变量:不序列化(属于类,而非对象实例)。
- 反序列化:不执行构造函数(直接恢复对象状态)。
serialVersionUID:显式定义版本号(如private static final long serialVersionUID = 1L),避免类结构修改后反序列化失败。
示例:对象序列化
1 | // 可序列化的类 |
最佳实践
- 选择合适的流类型:
- 二进制数据(图片、视频)→ 字节流(
FileInputStream/BufferedInputStream)。 - 文本数据 → 字符流(
BufferedReader/BufferedWriter),指定编码(如 UTF-8)。
- 二进制数据(图片、视频)→ 字节流(
- 使用缓冲流提升性能:缓冲流(
BufferedXXX)通过减少底层 I/O 调用,性能提升显著(尤其是大文件)。 - 优先使用
try-with-resources:自动关闭流,避免资源泄露(无需手动调用close())。 - 序列化注意事项:
- 敏感字段用
transient修饰。 - 显式定义
serialVersionUID保证版本兼容性。
- 敏感字段用
- 避免重复创建流:流的创建成本较高,频繁创建会降低性能(如循环中避免创建流)



v1.3.10