Java I/O 流完整指南
一、I/O 流基础
1. 什么是 I/O 流?
- 定义:数据输入输出的抽象模型,按顺序读写数据。
- 分类:
- 按方向:输入流(
InputStream/Reader
)、输出流(OutputStream/Writer
)。 - 按数据类型:
- 字节流:处理二进制数据(
InputStream/OutputStream
)。 - 字符流:处理文本数据(
Reader/Writer
)。
- 按功能:
- 节点流:直接操作数据源(如
FileInputStream
)。 - 处理流:包装节点流,增强功能(如
BufferedInputStream
)。
2. 设计模式:装饰器模式
二、字节流详解
1. 核心类与场景示例
类名 | 设计目的 | 场景示例 | 代码片段 |
---|
FileInputStream | 从文件读取字节 | 读取图片文件 | new FileInputStream("image.jpg") |
FileOutputStream | 向文件写入字节 | 保存二进制数据到文件 | new FileOutputStream("output.dat") |
ByteArrayInputStream | 从内存字节数组读取 | 将内存数据转换为输入流 | new ByteArrayInputStream(new byte[]{0x48, 0x65}) |
BufferedInputStream | 提升读取效率(缓冲) | 大文件高效读取 | new BufferedInputStream(new FileInputStream("large.zip"), 8192) |
DataInputStream | 读取基本数据类型 | 读取二进制协议数据(如int、double) | dis.readInt(); |
ObjectInputStream | 反序列化对象 | 从文件恢复Java对象 | Object obj = ois.readObject(); |
2. 原理
- 文件读取:调用操作系统API(如Linux的
read()
系统调用)。 - 缓冲机制:减少系统调用次数,提升性能(默认8KB缓冲区)。
3. 注意事项
- 资源泄漏:必须使用
try-with-resources
关闭流。 - 大文件处理:分块读取(如
byte[8192]
),避免内存溢出。
三、字符流详解
1. 核心类与场景示例
类名 | 设计目的 | 场景示例 | 代码片段 |
---|
FileReader | 按默认编码读取文本文件 | 读取UTF-8编码的配置文件 | new FileReader("config.txt") |
FileWriter | 按默认编码写入文本文件 | 写入日志文件 | new FileWriter("app.log", true) // true表示追加模式 |
BufferedReader | 支持按行读取文本 | 逐行解析CSV文件 | br.readLine(); |
InputStreamReader | 按指定编码转换字节流为字符流 | 读取GBK编码文件 | new InputStreamReader(new FileInputStream("gbk.txt"), "GBK") |
2. 编码与解码
- 核心问题:字符集不一致导致乱码。
- 解决方案:统一使用UTF-8,或通过
InputStreamReader
指定编码。
3. 注意事项
- 换行符处理:不同操作系统换行符不同(
\n
、\r\n
)。 - 性能优化:字符流默认无缓冲,需包装为
BufferedReader
。
四、打印流(PrintStream/PrintWriter)
1. 设计目的
- 功能:提供便捷的格式化输出方法(如
print()
、printf()
)。 - 与普通流的区别:
- 自动处理字符编码。
- 不会抛出
IOException
(通过 checkError()
检查错误)。
2. 核心类与场景示例
类名 | 设计目的 | 场景示例 | 代码片段 |
---|
PrintStream | 输出字节流(支持格式化) | 控制台输出(System.out) | System.out.println("Hello"); |
PrintWriter | 输出字符流(支持格式化) | 写入格式化的文本文件 | PrintWriter pw = new PrintWriter("log.txt"); pw.printf("Date: %tF", new Date()); |
3. 原理
- 自动刷新:启用
autoFlush
后,每次调用 println()
自动刷新缓冲区。 - 错误处理:通过
checkError()
静默捕获异常。
4. 注意事项
- 不自动刷新的风险:未调用
flush()
可能导致数据未写入。 - 性能:频繁调用
print()
时,包装为 BufferedWriter
提升性能。
五、NIO(New I/O)深入
1. 设计目标
- 解决传统I/O的问题:
- 阻塞模型:线程在读写时被阻塞。
- 性能瓶颈:频繁的系统调用和内存拷贝。
- 核心改进:
- 非阻塞I/O:通过
Selector
实现多路复用。 - 内存映射文件:直接操作内存,避免内核态与用户态数据拷贝。
2. 核心组件
组件 | 设计原理 | 场景示例 |
---|
Channel | 双向通信通道(替代单向流),支持异步读写。 | FileChannel 、SocketChannel |
Buffer | 数据容器(如 ByteBuffer ),提供高效的内存操作(get() 、put() )。 | 读写文件或网络数据 |
Selector | 多路复用器,单线程监听多个通道事件(连接、读、写)。 | 高并发网络服务器 |
3. 代码示例
try (FileChannel src = new FileInputStream("source.txt").getChannel();
FileChannel dest = new FileOutputStream("dest.txt").getChannel()) {
dest.transferFrom(src, 0, src.size());
}
Selector selector = Selector.open();
ServerSocketChannel ssc = ServerSocketChannel.open();
ssc.bind(new InetSocketAddress(8080));
ssc.configureBlocking(false);
ssc.register(selector, SelectionKey.OP_ACCEPT);
while (true) {
selector.select();
Set<SelectionKey> keys = selector.selectedKeys();
for (SelectionKey key : keys) {
if (key.isAcceptable()) {
SocketChannel client = ssc.accept();
client.configureBlocking(false);
client.register(selector, SelectionKey.OP_READ);
} else if (key.isReadable()) {
SocketChannel client = (SocketChannel) key.channel();
ByteBuffer buffer = ByteBuffer.allocate(1024);
client.read(buffer);
buffer.flip();
}
}
}
4. 注意事项
- Buffer状态管理:正确使用
flip()
(写→读切换)、clear()
(重置缓冲区)。 - Selector空轮询问题:某些JDK版本可能触发,需设置超时或升级JDK。
六、对象序列化
1. 核心机制
- 序列化:将对象转换为字节流(
ObjectOutputStream
)。 - 反序列化:从字节流恢复对象(
ObjectInputStream
)。
2. 高级技巧
3. 注意事项
- 敏感字段:使用
transient
禁止序列化(如密码)。 - 性能问题:序列化开销大,高频场景建议使用JSON或Protobuf替代。
七、最佳实践与常见问题
1. 资源管理
2. 性能优化
- 缓冲流:始终包装为
BufferedInputStream
或 BufferedReader
。 - 直接内存:使用
ByteBuffer.allocateDirect()
减少堆内存拷贝。
3. 调试技巧
- 字符乱码:
- 确认文件编码(
file -i filename
)。 - 检查
InputStreamReader
的字符集设置。 - 确保完整读取多字节字符(如UTF-8汉字)。
总结
Java I/O 流体系是处理数据输入输出的核心工具,涵盖从基础字节流到高性能NIO的全场景解决方案。关键要点包括:
- 理解流的设计模式:装饰器模式实现功能扩展。
- 正确选择流类型:字节流处理二进制,字符流处理文本。
- 掌握NIO非阻塞模型:适用于高并发网络服务。
- 严格资源管理:使用
try-with-resources
避免泄漏。 - 性能优化:缓冲、分块处理、零拷贝技术。
通过合理应用这些知识,可以构建高效、健壮的I/O密集型应用。