IO 与 NIO 学习总览
IO 是程序和外部世界交换数据的能力。文件读写、网络请求、上传下载、日志落盘、Socket 通信、数据库驱动、Redis 客户端、MQ 客户端,底层都离不开 IO。
零基础先记住一句话:
IO 解决“数据怎么进来、怎么出去”;NIO 解决“高并发连接下,如何少用线程也能处理很多 IO 事件”。
Java 学 IO 不能只看最新 JDK。企业项目和面试里最常见的是:
| 版本 | 重点 |
|---|---|
| JDK 7 | try-with-resources、Path、Files、WatchService、AsynchronousFileChannel,这是现代文件 API 的基础 |
| JDK 8 | 企业主流版本,常见项目大量使用 InputStream、Reader、Files、FileChannel、SocketChannel、Selector |
| JDK 11+ | Files.readString/writeString、HTTP Client 等更方便,但不能替代 JDK7/8 基础 |
本目录按学习顺序拆分:
| 文档 | 解决的问题 | 学完能做什么 |
|---|---|---|
| 传统 IO | 字节流、字符流、装饰器、阻塞读写 | 会安全读写文件、理解为什么会阻塞 |
| 缓冲与字符编码 | 缓冲为什么快,乱码怎么来 | 能处理 UTF-8 文本、避免大文件 OOM |
| IO 与 NIO 全过程原理 | 从 Java 代码到操作系统内核的完整链路 | 能讲清 read/write、阻塞、缓冲、Selector、零拷贝和排查过程 |
| NIO Buffer 与 Channel | Buffer 状态机、Channel 双向通道 | 会用 FileChannel 和 ByteBuffer |
| Selector 多路复用 | 一个线程如何管理多个连接 | 理解 Netty/Reactor 的前置知识 |
| 零拷贝与大文件 | transferTo、mmap、直接内存 | 能解释高性能文件传输和 DirectBuffer OOM |
| 排查与面试 | 高频问题、线上故障、面试回答 | 能把原理讲成项目经验 |
为什么要学 IO
很多线上问题表面看是接口慢、线程池满、服务假死,根因却是 IO 没理解:
| 现象 | 常见 IO 根因 | 后果 |
|---|---|---|
| 文件导入很慢 | 小块频繁读磁盘,没有缓冲 | CPU 不高但耗时长 |
| 上传接口卡死 | 阻塞读一直等客户端数据 | Tomcat 工作线程被占满 |
| 日志写不出来 | 磁盘满或句柄泄漏 | 排查时没有证据 |
| 中文乱码 | 字节编码和解码字符集不一致 | 数据入库错误 |
| 大文件导出 OOM | 一次性读入内存 | JVM 堆被打满 |
| Netty 直接内存 OOM | DirectBuffer 未释放或超限 | OutOfMemoryError: Direct buffer memory |
| NIO 服务 CPU 100% | Selector 空轮询或 OP_WRITE 使用错误 | 服务无响应但 CPU 很高 |
IO 的本质
程序读文件不是 Java 对象直接伸手去磁盘拿数据,而是经历操作系统内核。
mermaid
flowchart TD
A["Java 代码"] --> B["JVM 本地方法"]
B --> C["操作系统内核"]
C --> D["磁盘 / 网卡"]
D --> C
C --> E["内核缓冲区"]
E --> F["用户态缓冲区"]
F --> A这张图说明几个关键点:
- IO 往往会发生用户态和内核态切换。
- 磁盘、网络比内存慢得多,所以 IO 常常是等待外部资源。
- 缓冲区能减少系统调用次数和复制次数。
- NIO、零拷贝、直接内存,本质都是围绕“减少等待、减少线程、减少复制”做优化。
BIO、NIO、AIO 一句话区分
| 模型 | 全称 | 核心特点 | 适合场景 |
|---|---|---|---|
| BIO | Blocking IO | 同步阻塞,线程发起 IO 后一直等 | 连接少、逻辑简单、文件读写 |
| NIO | Non-blocking IO | 同步非阻塞,多路复用,一个线程监听多个 Channel | 高并发网络连接、网关、长连接 |
| AIO | Asynchronous IO | 异步 IO,操作系统完成后回调 | 部分文件/网络异步场景,Java 后端使用相对少 |
注意:NIO 的 N 在 Java 语境里常被理解为 New IO,也常被拿来讲 Non-blocking IO。面试时要说清楚:Java NIO 这套 API 包括 Buffer、Channel、Selector,其中网络 Channel 可以配置成非阻塞,并配合 Selector 实现多路复用。
同步、异步、阻塞、非阻塞别混了
这四个词经常被混用。可以从两个维度理解:
| 维度 | 关心的问题 | 例子 |
|---|---|---|
| 同步/异步 | 结果由谁来通知 | 同步是调用方自己等结果;异步是完成后回调/通知 |
| 阻塞/非阻塞 | 等待时线程是否被挂住 | 阻塞读不到数据就停住;非阻塞读不到就立即返回 |
mermaid
flowchart TD
A["发起 IO 调用"] --> B{"是否马上返回"}
B -- "不返回" --> C["阻塞"]
B -- "马上返回" --> D["非阻塞"]
A --> E{"完成结果谁来推动"}
E -- "调用方继续等待或轮询" --> F["同步"]
E -- "系统完成后通知回调" --> G["异步"]BIO 是同步阻塞;Java NIO 常见网络用法是同步非阻塞;AIO 是异步。
商业项目怎么用
| 场景 | 推荐能力 | 原因 |
|---|---|---|
| 小文件配置读取 | Files.newBufferedReader | 简单、安全、编码明确 |
| 大文件导入 | BufferedInputStream 或 FileChannel 分块读取 | 避免一次性读入内存 |
| 文件下载 | 分块写响应流,必要时使用 FileChannel.transferTo | 降低内存占用 |
| 医疗数据采集文件落盘 | 临时文件 + 校验 + 原子移动 | 避免半文件被业务读取 |
| 高并发长连接 | Netty,而不是手写复杂 NIO | 解决粘包、内存池、线程模型、连接管理 |
| 日志采集或网关 | NIO/Reactor/Netty | 大量连接不能一个连接一个线程 |
学习建议
- 先学传统 IO:字节流、字符流、缓冲、关闭资源。
- 再学编码:乱码本质是同一批字节被用错误字符集解释。
- 再看 IO 与 NIO 全过程原理:把
read/write、阻塞、缓冲、Selector、零拷贝和排查串起来。 - 再学 NIO:先把 Buffer 的
position/limit/capacity学明白。 - 再学 Selector:理解一个线程为什么能管理多个连接。
- 最后看零拷贝、直接内存、Netty,把原理和生产问题连起来。
面试标准回答
IO 是 Java 和文件、网络等外部资源交换数据的能力。传统 IO 面向流,通常是同步阻塞模型,代码简单但连接多时线程成本高。NIO 面向 Buffer 和 Channel,网络 Channel 可以配置非阻塞,再通过 Selector 多路复用,让一个线程监听多个连接的读写事件。文件处理常用缓冲流、Files、FileChannel;高并发网络服务一般不会手写 NIO,而是使用 Netty 这类成熟框架。生产中还要注意编码、资源关闭、大文件分块、直接内存和文件句柄泄漏。
