特性 | 传统 I/O (BIO) | NIO (New I/O) |
---|---|---|
模型 | 同步阻塞模型 | 同步非阻塞模型 |
数据流方向 | 单向流(InputStream/OutputStream) | 双向通道(Channel) |
数据操作单元 | 基于字节/字符流 | 基于缓冲区(Buffer) |
线程模型 | 一个连接一个线程 | 单线程管理多连接(Selector) |
适用场景 | 低并发、大数据量传输 | 高并发、短连接或长连接复用 |
BIO(阻塞IO):
线程调用 read()
或 write()
时会被阻塞,直到数据就绪或完成传输。
问题:高并发时需创建大量线程,导致资源耗尽。
NIO(非阻塞IO):
线程通过轮询检查通道(Channel)的就绪状态,未就绪时可处理其他任务。
优势:单线程可管理多个连接,减少线程上下文切换开销。
BIO:
基于流(Stream),数据单向流动(输入流只能读,输出流只能写)。
示例:FileInputStream
读取文件内容。
NIO:
基于通道(Channel)和缓冲区(Buffer),数据通过 Buffer
与 Channel
交互。
操作流程:
// 读取文件内容到 Buffer
FileChannel channel = new FileInputStream("data.txt").getChannel();
ByteBuffer buffer = ByteBuffer.allocate(1024);
channel.read(buffer); // 数据从 Channel 写入 Buffer
buffer.flip(); // 切换为读模式
特点:缓冲区可前后移动(flip()
, rewind()
),支持更灵活的数据处理。
NIO 核心组件:
Selector:单线程监听多个通道的事件(如连接就绪、读就绪、写就绪)。
SelectionKey:标识通道与Selector的注册关系及关注的事件类型。
工作流程:
Selector selector = Selector.open();
ServerSocketChannel serverChannel = ServerSocketChannel.open();
serverChannel.configureBlocking(false); // 非阻塞模式
serverChannel.register(selector, SelectionKey.OP_ACCEPT); // 注册ACCEPT事件
while (true) {
selector.select(); // 阻塞直到有事件就绪
Set keys = selector.selectedKeys();
for (SelectionKey key : keys) {
if (key.isAcceptable()) { // 处理新连接
SocketChannel client = serverChannel.accept();
client.configureBlocking(false);
client.register(selector, SelectionKey.OP_READ);
} else if (key.isReadable()) { // 处理读请求
SocketChannel client = (SocketChannel) key.channel();
ByteBuffer buffer = ByteBuffer.allocate(1024);
client.read(buffer);
// 处理数据...
}
}
keys.clear();
}
文件传输:需稳定传输大数据量的场景(如上传/下载文件)。
简单客户端程序:连接数少且业务逻辑简单(如内部工具)。
示例代码:
// 传统 Socket 服务端(每个连接一个线程)
ServerSocket serverSocket = new ServerSocket(8080);
while (true) {
Socket socket = serverSocket.accept(); // 阻塞等待连接
new Thread(() -> {
InputStream in = socket.getInputStream();
// 读取数据并处理...
}).start();
}
高并发服务器:如即时通讯、在线游戏、实时推送服务。
长连接复用:如 HTTP/2、WebSocket 等多路复用协议。
示例代码:
// NIO 多路复用服务端(单线程处理多连接)
Selector selector = Selector.open();
ServerSocketChannel serverChannel = ServerSocketChannel.open();
serverChannel.bind(new InetSocketAddress(8080));
serverChannel.configureBlocking(false);
serverChannel.register(selector, SelectionKey.OP_ACCEPT);
while (true) {
selector.select();
Iterator keys = selector.selectedKeys().iterator();
while (keys.hasNext()) {
SelectionKey key = keys.next();
if (key.isAcceptable()) {
// 处理新连接...
} else if (key.isReadable()) {
// 处理读事件...
}
keys.remove();
}
}
指标 | BIO | NIO |
---|---|---|
线程开销 | 高(每连接一线程) | 低(单线程多连接) |
CPU 利用率 | 低(线程阻塞等待) | 高(轮询就绪事件) |
内存消耗 | 高(大量线程栈内存) | 低(缓冲区复用) |
代码复杂度 | 简单 | 复杂(需处理缓冲区、选择器) |
选择 BIO:
连接数少(如 < 1000)且业务简单。
需快速开发,不追求极致性能。
选择 NIO:
高并发(如 > 10K 连接)或长连接场景。
需要低延迟和资源高效利用(如金融交易系统)。
扩展技术:
AIO(NIO.2):异步非阻塞模型(基于回调),适合文件IO或超高并发,但Java实现不如Netty成熟。
Netty 框架:基于NIO封装,简化开发,广泛应用于RPC、IM等场景(如Dubbo、RocketMQ)。
通过合理选择 I/O 模型,可显著提升系统吞吐量与稳定性!