Java IO 演进之路
Java IO 从基于流的阻塞 IO 演进到基于缓冲区的非阻塞 IO,是高并发网络编程的基础。
一、整体架构
Java IO 演进
├── BIO (Blocking IO) - JDK 1.0
│ └── 基于流,阻塞式
├── NIO (New IO) - JDK 1.4
│ └── 基于缓冲区,非阻塞 + 多路复用
└── NIO.2 (AIO) - JDK 7
└── 异步 IO
二、BIO(阻塞 IO)
2.1 核心特点
| 特点 | 说明 |
|---|---|
| 面向流 | 数据按顺序读写 |
| 阻塞式 | read/write 时线程等待 |
| 单向传输 | 输入流/输出流分离 |
2.2 体系结构
IO 流
├── 字节流
│ ├── InputStream(输入)
│ └── OutputStream(输出)
└── 字符流
├── Reader(输入)
└── Writer(输出)
2.3 使用示例
// 文件读取
try (InputStream in = new FileInputStream("data.txt");
BufferedInputStream bis = new BufferedInputStream(in)) {
byte[] buffer = new byte[1024];
int len;
while ((len = bis.read(buffer)) != -1) {
// 阻塞式读取
System.out.println(new String(buffer, 0, len));
}
}
2.4 缺点
- 线程利用率低:一个连接一个线程
- 高并发瓶颈:线程数受系统限制
- 无法处理海量连接:C10K 问题
三、NIO(非阻塞 IO)
3.1 核心组件
NIO 三剑客
├── Buffer(缓冲区) - 数据容器
├── Channel(通道) - 双向传输
└── Selector(选择器) - 多路复用
3.2 Buffer(缓冲区)
核心属性:
ByteBuffer buffer = ByteBuffer.allocate(1024);
// capacity: 总容量(固定)
// position: 当前位置
// limit: 读写边界
常用方法:
buffer.put(data); // 写入
buffer.flip(); // 切换读写模式
buffer.get(); // 读取
buffer.clear(); // 清空
3.3 Channel(通道)
特点:
- 双向传输(可读可写)
- 必须通过缓冲区操作
- 支持非阻塞模式
常用实现:
FileChannel // 文件读写
SocketChannel // TCP 客户端
ServerSocketChannel // TCP 服务端
DatagramChannel // UDP
3.4 Selector(选择器)
核心作用:单线程管理多个通道的可读/可写事件
工作流程:
// 1. 创建 Selector
Selector selector = Selector.open();
// 2. 注册通道(需非阻塞)
channel.configureBlocking(false);
channel.register(selector, SelectionKey.OP_READ);
// 3. 轮询事件
while (true) {
selector.select(); // 阻塞等待
Set<SelectionKey> keys = selector.selectedKeys();
for (SelectionKey key : keys) {
if (key.isReadable()) {
// 处理读事件
}
if (key.isWritable()) {
// 处理写事件
}
}
keys.remove();
}
四、BIO vs NIO
| 对比维度 | BIO | NIO |
|---|---|---|
| 数据模型 | 面向流 | 面向缓冲区 |
| 阻塞特性 | 阻塞 | 非阻塞 |
| 线程模型 | 一个连接一个线程 | 单线程多连接 |
| 适用场景 | 低并发、简单 IO | 高并发网络 |
五、NIO 实战
5.1 NIO 服务器
public class NioServer {
public static void main(String[] args) throws IOException {
ServerSocketChannel server = ServerSocketChannel.open();
server.bind(new InetSocketAddress(8080));
server.configureBlocking(false);
Selector selector = Selector.open();
server.register(selector, SelectionKey.OP_ACCEPT);
while (true) {
selector.select();
Iterator<SelectionKey> iterator = selector.selectedKeys().iterator();
while (iterator.hasNext()) {
SelectionKey key = iterator.next();
iterator.remove();
if (key.isAcceptable()) {
SocketChannel client = server.accept();
client.configureBlocking(false);
client.register(selector, SelectionKey.OP_READ);
} else if (key.isReadable()) {
SocketChannel client = (SocketChannel) key.channel();
ByteBuffer buffer = ByteBuffer.allocate(1024);
int len = client.read(buffer);
if (len > 0) {
buffer.flip();
client.write(buffer);
}
}
}
}
}
}
5.2 零拷贝
传统 IO:
磁盘 → 内核缓冲区 → 用户缓冲区 → Socket 缓冲区 → 网络
(4 次拷贝,4 次上下文切换)
NIO 零拷贝:
磁盘 → 内核缓冲区 → Socket 缓冲区 → 网络
(sendfile,2 次拷贝,2 次上下文切换)
实现:
FileChannel fileChannel = new FileInputStream("file.txt").getChannel();
SocketChannel socketChannel = SocketChannel.open();
// 零拷贝传输
fileChannel.transferTo(0, fileChannel.size(), socketChannel);
六、总结
Java IO 核心要点:
| 特性 | BIO | NIO |
|---|---|---|
| 核心 | 流(Stream) | 缓冲区(Buffer) |
| 阻塞 | 是 | 否 |
| 多路复用 | 否 | 是(Selector) |
| 适用 | 简单 IO | 高并发网络 |
NIO 是 Netty 等高性能框架的基础,理解 NIO 对掌握高并发编程至关重要。