你有没有遇到过这样的问题?
👉 用 ServerSocket 写了个服务器,结果只能同时处理一个客户端,第二个连不上?
👉 听同事说 “我们用 Netty,基于 NIO,性能高”,但你不知道 NIO 到底强在哪?
👉 面试官问:“说说 BIO、NIO、AIO 的区别?” 你脑子里蹦出几个词:“阻塞”、“非阻塞”、“异步”,但串不起来,说不完整……
这三个缩写,其实是 Java IO 模型的三代演进:
名称 | 全称 | 中文 | 出现时间 |
|---|---|---|---|
BIO | Blocking I/O | 阻塞式 I/O | Java 1.0 |
NIO | Non-blocking I/O | 非阻塞 I/O | Java 1.4 |
AIO | Asynchronous I/O | 异步 I/O | Java 7 |
它们的目标都一样:让程序高效地读写数据(文件、网络等)。 但实现方式完全不同,性能也天差地别。
想象一家小餐馆:
如果每桌吃饭要 1 小时,那 10 桌要 10 小时!效率极低。
在 Java BIO 中:
read() 时会阻塞,直到数据到来// BIO 服务器伪代码
ServerSocket server = new ServerSocket(8080);
while (true) {
Socket client = server.accept(); // 阻塞:等连接
new Thread(() -> {
InputStream in = client.getInputStream();
byte[] buf = new byte[1024];
int len = in.read(buf); // 阻塞:等数据
// 处理数据...
}).start();
}💡 BIO 适用于:连接数少、并发低的场景,如内部工具、小项目。
还是那家餐馆,但这次升级了:
传菜员不用傻等,可以同时盯 10 个厨房!
NIO 的三大核心组件:
InputStream/OutputStreamFileChannel:文件读写SocketChannel:TCP 客户端ServerSocketChannel:TCP 服务器DatagramChannel:UDPByteBuffer、CharBuffer 等flip()(读写切换)、clear()、compact()这是 NIO 的灵魂!
Selector 可以监听多个 Channel 的事件OP_ACCEPT:有新连接接入OP_READ:有数据可读OP_WRITE:可写数据(一般不监听)OP_CONNECT:连接建立完成// 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) {
// 阻塞等待,直到有事件发生
int readyChannels = selector.select();
// 获取所有就绪的事件
Set<SelectionKey> selectedKeys = selector.selectedKeys();
Iterator<SelectionKey> it = selectedKeys.iterator();
while (it.hasNext()) {
SelectionKey key = it.next();
if (key.isAcceptable()) {
// 有新连接
ServerSocketChannel server = (ServerSocketChannel) key.channel();
SocketChannel client = server.accept();
client.configureBlocking(false);
client.register(selector, SelectionKey.OP_READ);
}
if (key.isReadable()) {
// 有数据可读
SocketChannel client = (SocketChannel) key.channel();
ByteBuffer buffer = ByteBuffer.allocate(1024);
int len = client.read(buffer); // 非阻塞!
if (len > 0) {
buffer.flip();
// 处理数据...
}
}
it.remove(); // 必须移除
}
}💡 NIO 是 Netty、Redis、Kafka 等高性能框架的底层基石!
继续餐馆比喻:
整个过程,客人(程序)不用等待,也不用主动查,做好了自然会通知你。
在 Java AIO 中:
read() 后立即返回,不阻塞// AIO 服务器示例(基于回调)
AsynchronousServerSocketChannel server = AsynchronousServerSocketChannel.open();
server.bind(new InetSocketAddress(8080));
// 接受连接(异步)
server.accept(null, new CompletionHandler<AsynchronousSocketChannel, Object>() {
@Override
public void completed(AsynchronousSocketChannel client, Object attachment) {
// 新连接到来
server.accept(null, this); // 继续接受下一个
// 读数据(异步)
ByteBuffer buffer = ByteBuffer.allocate(1024);
client.read(buffer, null, new CompletionHandler<Integer, Object>() {
@Override
public void completed(Integer result, Object attachment) {
// 数据读取完成
buffer.flip();
// 处理数据...
}
@Override
public void failed(Throwable exc, Object attachment) {
// 读取失败
}
});
}
@Override
public void failed(Throwable exc, Object attachment) {
// 接受失败
}
});💡 AIO 适用于:对延迟极度敏感、连接数极高的场景,但实际应用不如 NIO 广泛。
特性 | BIO | NIO | AIO |
|---|---|---|---|
模型 | 同步阻塞 | 同步非阻塞 | 异步非阻塞 |
核心机制 | 每连接一线程 | 多路复用(Selector) | 回调 / Future |
是否阻塞 | 是(read/write 阻塞) | 否(select 阻塞,read/write 不阻塞) | 否 |
编程难度 | 简单 | 复杂 | 复杂 |
适用场景 | 低并发 | 高并发(Netty、Redis) | 极高并发(较少用) |
代表框架 | 传统 Servlet | Netty、Redis | Java 7+ AsynchronousChannel |
答:
答: 三大核心:
SocketChannel;答:
Selector底层依赖操作系统提供的 epoll(Linux) 或 kqueue(Mac) 等机制。 它将多个 Channel 注册到内核的事件表中,当某个 Channel 有事件就绪(如数据到达),内核会通知Selector,Java 程序再处理。 这样避免了轮询所有连接,极大提升了效率。
答: Netty 默认使用 NIO(基于
NioEventLoopGroup),因为 NIO 在 Linux 上性能稳定,且 API 成熟。 虽然 Netty 也支持 AIO(EpollEventLoopGroup),但实际生产中 NIO 更主流。 我们项目中用的是 NIO 模式。
问题 | 答案 |
|---|---|
BIO 问题 | 线程爆炸,不适合高并发 |
NIO 核心 | Channel + Buffer + Selector(多路复用) |
AIO 特点 | 异步回调,Linux 支持弱 |
谁性能最强 | 理论 AIO > NIO > BIO,但实际 NIO 最常用 |
面试怎么说 | “我理解 NIO 的多路复用原理,项目用 Netty(NIO)做高性能通信” |
BIO、NIO、AIO 不是“替代”,而是“演进”。 从“一客一线程”到“单线程管万连接”, 这背后是操作系统与编程模型的深度结合。 掌握它们,你才能真正理解“高并发系统是如何扛住百万请求的”。
希望这篇能帮你彻底搞懂 Java 的 IO 演进之路!