背景与动机
Java IO 基础 主要讲字节流、字符流和 Files,适合文件读写入门。
一旦进入高并发网络通信,就要从“流怎么读文件”切换到“IO 模型怎么等数据”:
- BIO:一个连接一个线程,简单但连接多时开销大
- NIO:一个线程通过
Selector监听多个连接 - AIO:把 IO 完成通知交给操作系统,业务侧回调处理
- Netty:在 NIO 之上封装事件驱动框架,简化高性能网络编程
核心原理拆解
1. 四个概念先分清
这是两套不同维度,不要混为一谈:

2. BIO、NIO、AIO 是什么
| 模型 | 含义 | 典型实现 | 适用场景 |
|---|---|---|---|
| BIO | 同步阻塞 IO | Socket + InputStream | 连接少、逻辑简单 |
| NIO | 同步非阻塞 IO | Channel + Buffer + Selector | 连接多、轻量读写(聊天、网关) |
| AIO | 异步非阻塞 IO | AsynchronousSocketChannel(NIO.2) | 连接多、重 IO(部分文件/网络场景) |
BIO 代码特征:server.accept()、in.read() 都会阻塞当前线程。
NIO 代码特征:通道设为非阻塞,由 Selector 轮询哪些连接可读/可写。
AIO 代码特征:调用异步 API 后注册 CompletionHandler,IO 完成后回调。

3. BIO、NIO、AIO 核心区别
| 对比项 | BIO | NIO | AIO |
|---|---|---|---|
| 阻塞性 | 阻塞 | 非阻塞(同步) | 异步非阻塞 |
| 编程难度 | 低 | 较高 | 高 |
| 连接模型 | 常一连接一线程 | 一线程多连接(多路复用) | 有效请求一线程(由 OS 通知) |
| 数据访问 | 面向流,单向 | 面向缓冲区 Buffer,双向 Channel | 同 NIO 体系 |
| JDK | 1.0 起 | 1.4 起 | 1.7 起 |
4. 五种 IO 模型(操作系统层面)
| 模型 | 说明 | 对应 Java 常见说法 |
|---|---|---|
| 阻塞 IO | 没数据就一直等 | BIO |
| 非阻塞 IO | 没数据立刻返回,应用轮询 | NIO 的非阻塞模式 |
| IO 多路复用 | 一个调用监听多个 fd,谁就绪处理谁 | NIO Selector |
| 信号驱动 IO | 数据就绪时发信号通知 | Java 较少直接使用 |
| 异步 IO | 内核完成数据拷贝后再通知应用 | AIO |
Selector 本质是多路复用:一个线程监听多个 Channel,避免“每个连接一个阻塞线程”。

5. 用户空间与内核空间
应用程序不能直接访问磁盘/网卡,IO 请求通常经过:
- 应用在自己的缓冲区准备数据
- 系统调用进入内核
- 内核与硬件交互完成读写
- 数据回到用户空间
理解这点有助于解释:为什么 BIO 会占着线程等内核、为什么 NIO 可以先知道“可读”再读。

6. NIO 三大核心组件
Channel(通道)
- 类似流,但可双向读写
- 常见:
SocketChannel、ServerSocketChannel
Buffer(缓冲区)
- 数据先进入 Buffer,再与 Channel 交互
- 底层是数组,有
position、limit、capacity - 读写前注意
flip()、clear()
Selector(选择器)
- 把多个 Channel 注册到同一个 Selector
- 通过
select()获取就绪事件(OP_ACCEPT、OP_READ、OP_WRITE) - 一个线程可处理多个连接

Selector selector = Selector.open();serverChannel.configureBlocking(false);serverChannel.register(selector, SelectionKey.OP_ACCEPT);
while (true) { if (selector.select() == 0) continue; for (SelectionKey key : selector.selectedKeys()) { if (key.isAcceptable()) { /* accept 新连接 */ } if (key.isReadable()) { /* read buffer */ } }}7. 网络 IO 编程怎么演进
| 阶段 | 做法 | 问题 |
|---|---|---|
| BIO 单线程 | accept + read 都在一个线程 | 一个客户端阻塞,其他客户端进不来 |
| BIO 多线程 | 每来一个连接 new Thread | 连接多时线程爆炸、上下文切换开销大 |
| BIO 线程池 | 连接交给线程池 | 缓解创建线程成本,但线程仍大量阻塞在 IO 上 |
| NIO | Selector 多路复用 | 少量线程处理大量连接,但 API 繁琐 |
| Netty | 封装 NIO + 事件驱动 | 开发效率高,生产级网络框架 |
结论:连接数少用 BIO 可以;高并发网络服务应理解 NIO 模型,实际项目多用 Netty。

8. Netty 是什么
Netty 是基于 NIO 的异步事件驱动网络框架,用于快速开发高性能、高可靠的服务端/客户端。
它解决的是原生 NIO 的问题:
Selector空轮询、连接断线重连等边界要自己处理- Buffer 拆分粘包/半包要自己设计协议
- 线程模型、Pipeline 编排复杂
9. Netty 核心概念
| 组件 | 作用 |
|---|---|
EventLoopGroup | 事件循环线程组,boss 接连接,worker 处理 IO |
ServerBootstrap / Bootstrap | 服务端/客户端启动辅助类 |
Channel | 网络连接抽象 |
ChannelPipeline | Handler 链,像过滤器/拦截器 |
ChannelHandler | 真正业务逻辑,如解码、鉴权、路由 |

典型服务端骨架:
EventLoopGroup bossGroup = new NioEventLoopGroup(1);EventLoopGroup workerGroup = new NioEventLoopGroup();try { ServerBootstrap b = new ServerBootstrap(); b.group(bossGroup, workerGroup) .channel(NioServerSocketChannel.class) .childHandler(new ChannelInitializer<SocketChannel>() { @Override protected void initChannel(SocketChannel ch) { ch.pipeline().addLast(new StringDecoder()); ch.pipeline().addLast(new StringEncoder()); ch.pipeline().addLast(new SimpleServerHandler()); } }); b.bind(8000).sync().channel().closeFuture().sync();} finally { bossGroup.shutdownGracefully(); workerGroup.shutdownGracefully();}业务代码通常写在继承 ChannelInboundHandlerAdapter 的 Handler 里,类似 Web 框架里的 Controller。
常见陷阱与错误示例
1. 把 NIO 当成“异步 IO”
NIO 的 read()/write() 本身仍是同步的,只是通过多路复用减少阻塞等待;真正异步是 AIO / Netty 的事件回调模型。
2. Buffer 忘记 flip
写满 Buffer 后直接读,或读完不 clear(),会导致数据错乱。
3. Selector 处理完不 remove key
selectedKeys 里的 key 处理完要 iterator.remove(),否则可能重复处理。
4. 高并发仍用 BIO 一连接一线程
连接上千后,线程栈内存和上下文切换会成为瓶颈。
面试速查(35 题精简版)
IO 与模型
- 什么是 IO:程序通过流/通道与外部设备(文件、网络)交换数据。
- 同步 vs 异步:同步需自己等结果;异步提交后由系统回调通知。
- 阻塞 vs 非阻塞:阻塞时线程挂起等待;非阻塞时立刻返回,可干别的。
- 什么是 BIO:同步阻塞,常一连接一线程。
- 什么是 NIO:同步非阻塞,Channel + Buffer + Selector 多路复用。
- 什么是 AIO:异步非阻塞,JDK 1.7+ NIO.2,基于回调。
- 什么是 Netty:基于 NIO 的事件驱动网络框架。
- BIO/NIO/AIO 区别:见上文对比表;BIO 面向流,NIO 面向 Buffer/Channel。
- IO 流怎么分类:流向、字节/字符、节点流/处理流(详见 IO 基础笔记)。
- 五种 IO 模型:阻塞、非阻塞、多路复用、信号驱动、异步。
- 内核空间:OS 高权限内存,应用 IO 需系统调用进入内核。
基础 IO 补充
- Bit / Byte / Char:Bit 最小二进制位;Byte 8 bit;Java
char16 bit Unicode。 - 序列化 / 反序列化:对象 ↔ 二进制;实现
Serializable,建议自定义serialVersionUID。 serialVersionUID作用:反序列化时校验类版本,不一致抛InvalidClassException。BufferedReader属于哪种流:字符处理流,带缓冲,常用readLine()。- 流超类:
InputStream、OutputStream、Reader、Writer。 - 二进制为何用字节流:文件/图片/音视频底层都是字节存储。
网络 IO 演进
- BIO 网络问题:
accept/read阻塞,单线程时其他连接饿死。 - 多线程 BIO:每连接一线程,连接多时资源耗尽。
- 线程池 BIO:缓解线程创建,但线程仍阻塞在 IO 上。
- NIO 如何解决:Selector 监听多连接,就绪再处理。
- Channel 特点:双向、可非阻塞、配合 Buffer。
- Selector 作用:注册多个 Channel,轮询就绪事件。
- Buffer 作用:数据中转数组,减少直接碰内核次数。
Netty
- Netty 与 NIO 关系:Netty 封装并增强 NIO,简化 API。
- 为什么用 Netty:高性能、成熟线程模型、Pipeline、粘包半包、社区生态。
EventLoopGroup做什么:事件循环线程,处理 IO 事件。ChannelPipeline做什么:Handler 责任链,编解码 + 业务。ChannelHandler做什么:处理读/写/异常等事件,写业务逻辑。- Boss / Worker 分工:Boss 接连接,Worker 处理 IO 与业务(经典双 Group 模型)。
- Netty 是否异步:是事件驱动异步模型,底层基于 NIO。
选型
- 什么时候用 BIO:连接少、开发简单、内网低并发。
- 什么时候用 NIO:高并发、短连接、轻量请求。
- 什么时候用 AIO:连接多且 IO 重,且运行环境对 AIO 支持好(实际 Netty 更常见)。
- 生产环境常见选择:网络服务多数用 Netty(或 Spring WebFlux 等基于 NIO 的框架)。
面试高频问题
1. BIO、NIO、AIO 有什么区别
BIO 同步阻塞,一连接一线程;NIO 同步非阻塞,Selector 多路复用;AIO 异步非阻塞,内核完成后回调。
2. 为什么高并发网络服务不推荐 BIO
线程阻塞在 IO 上,连接数增长会导致大量线程和上下文切换,CPU 浪费在等待而非计算。
3. NIO 三大组件是什么
Channel、Buffer、Selector。
4. Netty 解决了 NIO 的什么问题
简化多路复用、线程模型、协议编解码、连接生命周期和粘包半包等工程问题。
一句话总结
文件 IO 先学流和 Files;网络 IO 要分清 BIO/NIO/AIO 模型,理解 Selector 多路复用,生产级高并发网络开发重点掌握 Netty 的事件驱动与 Pipeline 模型。