目录
一、概念
1.1 I/O工作本质
1.2 BIO&NIO&AIO
二、BIO&NIO&AIO工作模式
2.1 BIO(Blocking IO阻塞式I/O)
2.2 NIO(Non-blocking IO 或 New IO非阻塞式I/O)
2.3 AIO(Asynchronous IO,也叫 NIO.2异步I/O)
三、应用模式
3.1 AIO(异步+回调)
3.1.1 为什么AIO"天生场景窄"(尤其Java)?
3.2 BIO(阻塞)
3.2.1 核心模式
3.2.2 适用场景(底层逻辑实现)
3.2 3 为什么用 BIO?
3.3 NIO(非阻塞+多路复用)
3.3.1 核心模式
3.3.2 适用场景(底层逻辑实现)
3.3.3 为什么用 NIO?
一、概念
在讲解I/O前不妨通过一个计算机工作的例子开始:
用记事本打开一个txt文件,计算机是怎么工作的呢?
- 应用程序(记事本)需要 “读取文件内容”—— 这是应用的需求;
- 应用程序通过系统调用(比如 read() 函数)发起 I/O 请求 —— 这是应用 “操作 I/O” 的开始;
- 操作系统接收到请求后,调度磁盘控制器读取数据,完成后把数据返回给应用 —— 这是 I/O 动作的执行过程(由系统完成,但由应用触发);
- 应用程序拿到数据后显示在窗口上 —— 应用处理 I/O 的结果。
从上面这个简单的例子中也可以看出:整个过程中,I/O(数据从磁盘到内存的传输)是 “ 应用程序为了显示内容而触发的一个必要动作”,它本身 不会主动 “工作”,必须 由应用程序先发起请求。
1.1 I/O工作本质
I/O(输入 / 输出)的核心是 “数据在不同设备 / 空间之间的传输”(比如内存与磁盘、内存与网卡、内存与键盘等)。但这种传输不会 “自动发生”,必须由某个主体(几乎都是应用程序)触发 —— 就像 “水不会自己从水箱流到杯子里”,必须有人打开水龙头(应用程序发起请求),水管(操作系统)才会把水(数据)送过来。
哪怕是看似 “自动” 的 I/O(比如键盘输入时数据进入内存),本质也是:应用程序(比如输入法、文本编辑器)提前通过系统调用 “注册了对键盘事件的监听”,当你按键时,操作系统才会把按键数据 “推” 给这个应用 —— 本质还是 应用程序先 “声明了需求”,I/O 才会定向发生。
所以结论很明确:
所有 I/O 都是 “应用程序为了完成自身任务而主动触发的操作”,I/O 本身是 “被触发的动作”,而非独立的 “工作主体”。BIO、NIO、AIO 只是应用程序在触发 I/O 后,处理 “等待过程” 和 “结果获取” 的不同方式,本质上仍是 “应用操作 I/O” 的细化分类。
1.2 BIO&NIO&AIO
严格意义来说,“AIO、BIO、NIO” 是应用程序层面对 I/O 操作的 “编程模式”-- 应用层模式,而 非操作系统原生定义的 I/O 模式。
操作系统层面只定义了 “同步 / 异步”“阻塞 / 非阻塞” 的底层 I/O 机制(这是更基础的分类):
- 同步 I/O:应用需要主动参与 I/O 过程(比如自己去取数据);
- 异步 I/O:应用只需发起请求,后续由操作系统完成所有工作,最后通知应用;
- 阻塞 I/O:I/O 未完成时,应用进程被挂起(不能执行);
- 非阻塞 I/O:I/O 未完成时,应用可继续执行其他任务。
而 BIO、NIO、AIO 是应用程序基于这些底层机制封装的 “使用方式”:
- BIO 是 “同步阻塞” 的典型实现(应用阻塞等待,且需主动处理结果);
- NIO 是 “同步非阻塞” 的优化(结合多路复用,避免无效等待);
- AIO 是 “异步非阻塞” 的实现(完全交给操作系统,应用 “躺平” 等通知)。
简单理解: 操作系统提供 “原材料”(底层 I/O 机制),BIO/NIO/AIO 是应用程序用这些原材料做出的 “不同菜式”—— 核心都是 I/O,但使用方式和效率不同。
二、BIO&NIO&AIO工作模式
这三种 IO 方式,本质上是在问“ 数据还没到的时候,线程怎么办”。
2.1 BIO(Blocking IO阻塞式I/O)
最原始也最好理解:
- 读:read() 不到数据就一直睡,直到内核把数据准备好并拷到用户缓冲区,才醒来返回。
- 写:缓冲区满就一直睡,直到内核把数据拷走。特点:代码简单,但一个连接就得配一个线程,高并发时线程爆炸。
2.2 NIO(Non-blocking IO 或 New IO非阻塞式I/O)
由 JDK 1.4 引入,核心是把“能不能做 IO”这件事暴露给程序:
- 线程可以先问 Channel:readable()? writable()?
- 如果没有数据,调用立即返回一个错误码,线程可以去做别的事,过一会儿再来问。
- 配合 Selector,一个线程能“批量询问”上千个 Channel,谁 ready 了就处理谁。特点:
- 单线程可管理海量连接(C10k 不是问题)。
- 代码比 BIO 复杂,需要手动维护状态机。
2.3 AIO(Asynchronous IO,也叫 NIO.2异步I/O)
JDK 7 引入,把“数据拷贝完成”也托管给系统:
- 线程发起 read(buf, callback) 或 write(buf, callback) 后直接返回。
- 内核把数据准备好并拷到用户缓冲区后,再回调用户代码。特点:
- 真正的“纯异步”,连“数据拷贝”都不阻塞应用线程。
- 需要操作系统支持(Windows IOCP 很好,Linux 早期是 epoll 线程池模拟,新版内核有 io_uring)。
- 写代码最简单,回调或 Future 即可;但调试和异常处理比 NIO 抽象。
- BIO:傻等,一连接一线程。
- NIO:不断问,自己选,一 Selector 管万连接。
- AIO:别问了,弄好了叫你。
三、应用模式
3.1 AIO(异步+回调)
目前几乎没有开发者用AIO来实现Java的应用场景,太过繁琐,而且会报莫名其妙的错。
3.1.1 为什么AIO"天生场景窄"(尤其Java)?
①有更优的替换方案
Java 中 NIO 框架(如 Netty)通过 “多路复用 + 线程池” 已能完美支撑高并发(如百万级连接),性能足够强,且代码成熟、易维护。相比之下,AIO 的 “异步回调” 逻辑复杂(需处理回调顺序、异常捕获),且 在 Java 中的实现对操作系统依赖深(注:AIO工作需要操作系统主动回复)
②场景被NIO替代
Java 最核心的 I/O 场景是 “网络通信”(如服务器开发),而网络 I/O 的瓶颈在于 “连接数和事件响应”,NIO 的 Selector 多路复用已能高效处理(一个线程监听所有连接的事件);即使是文件 I/O,普通场景用 BIO(小文件)或 NIO 的 FileChannel(大文件阻塞读写 + 内存映射)已足够,无需 AIO(使用AIO还麻烦)。
③生态支持差
主流框架(如 Spring、Netty、Tomcat)均未采用 AIO 模式,开发者自然不会在底层逻辑中使用(避免踩坑且无社区支持)。
【而且这只会是一个恶性循环,越没有人用,就越没有人用】
3.2 BIO(阻塞)
3.2.1 核心模式
- 阻塞特性:发起 I/O 操作(如读 Socket、读文件)后,线程会阻塞至操作完成(期间无法执行其他任务)。
- 设计逻辑:“一对一” 处理(一个连接 / 操作对应一个线程),逻辑直观,无需处理复杂的状态判断。
3.2.2 适用场景(底层逻辑实现)
适合 低并发、连接数少、操作耗时短 的场景,核心优势是 “代码简单、不易出错”:
- 简单 Socket 通信底层:如早期客户端 - 服务器交互(如一对一聊天工具),用单独线程处理单个连接的读写,阻塞等待数据到达(因连接少,线程资源不浪费)。
- 本地小文件处理:如配置文件加载(用 FileInputStream),读取耗时极短,阻塞对性能影响可忽略,无需复杂设计。
- 低频率批量任务:如日志写入(阻塞写入文件,无需实时响应,逻辑简单)。
3.2 3 为什么用 BIO?
不是因为高效,而是场景 “允许阻塞”:连接少则线程资源不紧张,操作快则阻塞时间可忽略,优先保证开发效率(避免复杂逻辑)。
3.3 NIO(非阻塞+多路复用)
3.3.1 核心模式
- 非阻塞 + 多路复用:
- 非阻塞:发起 I/O 后线程不阻塞,可处理其他任务(需主动判断操作是否完成)。
- 多路复用:通过 Selector(多路复用器)监听多个 I/O 通道(如 Socket 连接)的事件(可读、可写),集中处理就绪的操作。
- 设计逻辑:“少对多” 处理(少量线程通过 Selector 管理大量连接),基于事件驱动。
3.3.2 适用场景(底层逻辑实现)
适合 高并发、连接数多、操作耗时不确定 的场景,核心优势是 “高效利用线程资源”:
- 高性能网络服务器底层:如 Netty、Tomcat 的 NIO 模式,用 Selector 监听上万连接的事件,少量线程即可处理所有读写(避免 BIO 线程爆炸问题)。
- 大文件 / 流处理:如断点续传、视频流传输(用 FileChannel 非阻塞分块读写,结合 Selector 监听网络可写事件,避免数据堆积)。
- 分布式框架通信层:如 Redis、Kafka 客户端,用 Selector 同时监听多个节点的响应事件,无需为每个节点开阻塞线程。
3.3.3 为什么用 NIO?
解决高并发下的资源浪费问题:
- 连接数多(如 1 万连接):BIO 需要 1 万线程(阻塞),导致内存 / 线程切换开销爆炸;NIO 用 1-10 个线程即可。
- 操作耗时不确定(如网络延迟):BIO 线程长期阻塞浪费资源;NIO 线程可在等待时处理其他就绪连接。