这是一份非常详细且权威的 Java 本地 I/O 编程指南,涵盖Java 本地 I/O 编程的各个方面,并提供必要的实战代码。
目录
- 背景与前世今生
- 1.1 什么是 IO?
- 1.2 Java IO 的演变历程
- 1.3 Java NIO 的诞生背景
- 基础知识
- 2.1 流 (Stream) 的概念
- 2.2 字节流 vs 字符流
- 2.3 输入流 vs 输出流
- 2.4 节点流 vs 处理流 (包装流)
- 2.5 标准输入/输出/错误
- 2.6 File 类
- 核心知识点 (Java IO & NIO)
- 3.1 Java IO (
java.io)- 3.1.1 核心类解读 (
InputStream,OutputStream,Reader,Writer) - 3.1.2 常用节点流 (
FileInputStream,FileOutputStream,FileReader,FileWriter) - 3.1.3 常用处理流 (
BufferedInputStream,BufferedOutputStream,BufferedReader,BufferedWriter,ObjectInputStream,ObjectOutputStream,PrintWriter) - 3.1.4 序列化与反序列化
- 3.1.1 核心类解读 (
- 3.2 Java NIO (
java.nio)- 3.2.1 核心概念 (Buffer, Channel, Selector)
- 3.2.2 Buffer 详解 (属性、类型、操作)
- 3.2.3 Channel 详解 (
FileChannel,SocketChannel,ServerSocketChannel,DatagramChannel) - 3.2.4 Selector 与非阻塞 IO (Reactor 模式)
- 3.2.5 Path 与 Files (替代 File 类)
- 3.1 Java IO (
- 调优与性能优化
- 4.1 缓冲区 (Buffering) 的重要性
- 4.2 减少系统调用次数
- 4.3 内存映射文件 (
MappedByteBuffer) - 4.4 直接缓冲区 (Direct Buffer) vs 堆缓冲区 (Heap Buffer)
- 4.5 文件锁 (
FileLock) - 4.6 零拷贝技术
- 优缺点横向对比
- 5.1 Java IO (
java.io) vs Java NIO (java.nio)- 模型 (阻塞 vs 非阻塞/事件驱动)
- 性能 (小文件 vs 大文件 vs 高并发)
- 易用性
- 5.2 Java NIO 与 Netty
- 5.1 Java IO (
- 算法
- 6.1 文件遍历算法 (递归 vs NIO Files.walk)
- 6.2 文件内容查找 (逐行读取 vs 内存映射)
- 6.3 大文件处理策略 (分块读取/写入)
- 6.4 文件校验 (MD5, SHA)
- 避坑指南
- 7.1 资源泄漏 (
close()方法调用,Try-with-Resources) - 7.2 字符编码问题
- 7.3 文件路径问题 (相对路径 vs 绝对路径)
- 7.4 文件锁的正确使用
- 7.5
MappedByteBuffer的释放问题 - 7.6
Selector的空轮询问题
- 7.1 资源泄漏 (
- 应用场景
- 8.1 配置文件读写
- 8.2 日志文件记录
- 8.3 文件上传/下载
- 8.4 数据持久化 (序列化对象)
- 8.5 本地数据库交互 (如 SQLite)
- 8.6 高性能网络服务器 (结合 NIO)
- 并发与高可用
- 9.1 Java IO 的并发限制 (阻塞模型)
- 9.2 Java NIO 的高并发能力
- 9.3 线程池与 IO 操作
- 9.4 文件锁 (
FileLock) 与并发控制 - 9.5 高可用文件存储策略 (RAID, 分布式文件系统)
- QPS & TPS
- 10.1 定义
- 10.2 影响本地 IO QPS/TPS 的因素
- 磁盘类型 (HDD, SSD)
- 文件大小
- 缓冲区大小
- IO 模型 (BIO vs NIO)
- 是否使用零拷贝
- 10.3 测试方法 (JMH)
- Spring Boot 项目实战
- 11.1 配置文件读取 (
@Value,@ConfigurationProperties) - 11.2 文件上传实现 (MultipartFile)
- 11.3 文件下载实现 (ResponseEntity)
- 11.4 日志记录 (Logback, Log4j2)
- 11.5 使用 Spring Content 管理内容存储
- 11.6 集成 NIO 进行高性能文件处理 (示例)
- 11.1 配置文件读取 (
- 总结
1. 背景与前世今生
1.1 什么是 IO?
IO (Input/Output),即输入/输出,是指程序与外部世界(如文件系统、网络连接、用户输入设备、输出设备等)进行数据交换的过程。在 Java 中,主要关注的是文件 IO 和网络 IO。本指南重点讨论本地文件 IO。
1.2 Java IO 的演变历程
- Java 1.0 (1996): 引入了最初的
java.io包。核心是流 (Stream) 的概念,分为字节流 (InputStream,OutputStream) 和字符流 (Reader,Writer)。这是阻塞式 (Blocking IO) 模型:当一个线程执行读或写操作时,如果数据没有准备好(如磁盘未响应),该线程会被挂起,直到操作完成。这种模型简单易用,但在高并发场景下需要大量线程支持,资源消耗大。 - Java 1.4 (2002): 引入了
java.nio(New IO) 包。核心是 Buffer (缓冲区), Channel (通道) 和 Selector (选择器)。NIO 支持 非阻塞模式 (Non-blocking Mode) 和基于 事件驱动 (Event-driven) 的 IO 多路复用 (Multiplexing) 模型 (使用Selector)。这种模型允许一个线程管理多个 Channel,显著提高了在高并发连接下的性能和可伸缩性。 - Java 7 (2011): 引入了
java.nio.file包,提供了Path和Files等工具类,极大地简化了文件操作,功能也更强大(如文件属性、符号链接、目录遍历等),是对java.io.File的现代化替代。 - 后续发展: Java 7 还引入了 AsynchronousFileChannel,提供了异步文件 IO 操作。Java NIO 也成为构建高性能网络框架(如 Netty)的基础。
1.3 Java NIO 的诞生背景
传统的阻塞式 IO (BIO) 模型在高并发场景下(如需要同时处理成千上万个网络连接)存在瓶颈:
- 线程开销大: 每个连接需要一个线程,线程的创建、销毁、上下文切换开销巨大。
- 资源浪费: 线程在等待 IO 操作完成时处于阻塞状态,CPU 资源被闲置。
NIO 的非阻塞和 IO 多路复用模型旨在解决这些问题:
- 非阻塞: 线程发起 IO 请求后立即返回,不会被阻塞。数据准备好后,线程再进行处理。
- IO 多路复用: 一个线程(通过
Selector)可以监听多个Channel上的事件(读就绪、写就绪、连接就绪等)。当有事件发生时,线程才进行相应处理,大大提高了线程利用率。
2. 基础知识
2.1 流 (Stream) 的概念
流是一个有序的字节序列或字符序列,具有方向性(输入或输出)。Java IO 将数据的读写抽象为流的操作,屏蔽了底层设备的差异。
2.2 字节流 vs 字符流
- 字节流 (
InputStream,OutputStream): 以字节为单位(8位)读写数据。适用于所有类型的数据(图片、音频、视频、二进制文件等)。 - 字符流 (
Reader,Writer): 以字符为单位(16位 Unicode)读写数据。专为处理文本数据设计,能自动处理字符编码(如 UTF-8, GBK)。
选择原则: 操作二进制数据(如图片)用字节流;操作文本数据用字符流。
2.3 输入流 vs 输出流
- 输入流 (
InputStream,Reader): 从数据源(如文件)读取数据到程序。 - 输出流 (
OutputStream,Writer): 将程序中的数据写入到目的地(如文件)。
2.4 节点流 vs 处理流 (包装流)
- 节点流: 直接与特定的数据源或目的地(如文件、内存数组、管道)相连。例如
FileInputStream,FileOutputStream,FileReader,FileWriter。 - 处理流 (包装流): 对已有的流进行包装,提供额外的功能(如缓冲、转换编码、对象序列化)。例如
BufferedInputStream,BufferedReader,InputStreamReader,ObjectInputStream。处理流是装饰器模式的应用。
// 节点流直接操作文件
FileInputStream fis = new FileInputStream("source.txt");
FileOutputStream fos = new FileOutputStream("dest.txt");
// 处理流包装节点流,提供缓冲功能 (提高效率)
BufferedInputStream bis = new BufferedInputStream(fis);
BufferedOutputStream bos = new BufferedOutputStream(fos);
// 处理流包装字节流,提供按字符读取功能 (并指定编码)
BufferedReader br = new BufferedReader(new InputStreamReader(fis, "UTF-8"));
2.5 标准输入/输出/错误
Java 提供了三个标准的流对象:
System.in: 标准输入流 (通常是键盘输入),类型是InputStream。System.out: 标准输出流 (通常是控制台),类型是PrintStream。System.err: 标准错误输出流 (通常是控制台),类型是PrintStream。
// 从标准输入读取 (通常包装成 BufferedReader)
BufferedReader stdIn = new BufferedReader(new InputStreamReader(System.in));
String userInput = stdIn.readLine();
// 输出到标准输出
System.out.println("Hello, World!");
// 输出到标准错误
System.err.println("An error occurred!");
2.6 File 类
java.io.File 类代表文件和目录路径名的抽象表示。它可以用于:
- 创建文件/目录
- 删除文件/目录
- 重命名文件/目录
- 判断文件/目录是否存在
- 获取文件属性(大小、最后修改时间等)
- 列出目录内容
File file = new File("test.txt");
if (file.exists()) {
System.out.println("File name: " + file.getName());
System.out.println("File size: " + file.length() + " bytes");
System.out.println("Is directory: " + file.isDirectory());
System.out.println("Last modified: " + new Date(file.lastModified()));
} else {
file.createNewFile(); // 创建文件
}
注意: Java 7 引入了更强大的 java.nio.file.Path 和 java.nio.file.Files,推荐在新代码中使用它们替代 File。
3. 核心知识点 (Java IO & NIO)
3.1 Java IO (java.io)
3.1.1 核心类解读
InputStream(抽象类): 所有字节输入流的父类。- 核心方法:
int read(): 读取一个字节,返回读取的字节值(0-255),如果到达流末尾返回 -1。int read(byte[] b): 读取最多b.length个字节到数组b,返回实际读取的字节数,如果到达流末尾返回 -1。int read(byte[] b, int off, int len): 读取最多len个字节到数组b从偏移off开始的位置。void close(): 关闭流,释放资源。必须调用! (推荐使用try-with-resources)
- 核心方法:
OutputStream(抽象类): 所有字节输出流的父类。- 核心方法:
void write(int b): 写入一个字节(低8位)。void write(byte[] b): 写入整个字节数组b。void write(byte[] b, int off, int len): 写入字节数组b从偏移off开始的len个字节。void flush(): 刷新输出流,强制写出缓冲区中的数据(如果流支持缓冲)。void close(): 关闭流,释放资源。必须调用! (推荐使用try-with-resources)
- 核心方法:
Reader(抽象类): 所有字符输入流的父类。- 核心方法:
int read(): 读取一个字符,返回读取的字符值(0-65535),如果到达流末尾返回 -1。int read(char[] cbuf): 读取最多cbuf.length个字符到数组cbuf,返回实际读取的字符数,如果到达流末尾返回 -1。int read(char[] cbuf, int off, int len): 读取最多len个字符到数组cbuf从偏移off开始的位置。void close(): 关闭流,释放资源。
- 核心方法:
Writer(抽象类): 所有字符输出流的父类。- 核心方法:
void write(int c): 写入一个字符。void write(char[] cbuf): 写入整个字符数组cbuf。void write(char[] cbuf, int off, int len): 写入字符数组cbuf从偏移off开始的len个字符。void write(String str): 写入整个字符串str。void write(String str, int off, int len): 写入字符串str从偏移off
- 核心方法:
转载自CSDN-专业IT技术社区
原文链接:https://blog.csdn.net/suodasheng/article/details/157332228



