JavaIO笔记
一、BIO(阻塞 IO)版本
import java.io.IOException;
import java.io.InputStream;
import java.net.ServerSocket;
import java.net.Socket;public class BIOServer {public static void main(String[] args) throws IOException {// 1️⃣ 用户态:创建服务端Socket(底层通过syscall创建socket fd)ServerSocket serverSocket = new ServerSocket(8080);System.out.println("BIO服务器启动,监听8080...");while (true) {// 2️⃣ 阻塞等待客户端连接(accept是系统调用,会进入内核态)Socket socket = serverSocket.accept(); // <-- 阻塞点System.out.println("客户端已连接:" + socket.getRemoteSocketAddress());// 3️⃣ 读取客户端发送的数据(若内核缓冲区为空则阻塞)InputStream inputStream = socket.getInputStream();byte[] buffer = new byte[1024];int len;while ((len = inputStream.read(buffer)) != -1) { // <-- 阻塞点String msg = new String(buffer, 0, len);System.out.println("收到客户端消息:" + msg);}// 4️⃣ 客户端断开socket.close();}}
}内核交互图
用户线程(Java层) 操作系统(内核态)
----------------------------------------------------
accept() ------------> 内核等待新连接队列非空(若无连接则阻塞线程)read() ------------> 内核TCP缓冲区中若无数据 → 阻塞线程若有数据 → 拷贝至用户空间 → 返回结论:
BIO 每次 I/O 调用都会让线程阻塞,CPU 不能干别的事,因此每个连接都要一个线程去读。
二、NIO(非阻塞 IO + 多路复用)版本
我们使用 ServerSocketChannel + Selector 的典型写法👇
import java.io.IOException;
import java.net.InetSocketAddress;
import java.nio.ByteBuffer;
import java.nio.channels.*;
import java.util.Iterator;public class NIOServer {public static void main(String[] args) throws IOException {// 1️⃣ 创建 ServerSocketChannel(本质上仍是一个socket fd)ServerSocketChannel serverChannel = ServerSocketChannel.open();serverChannel.bind(new InetSocketAddress(8080));serverChannel.configureBlocking(false); // 设置为非阻塞模式System.out.println("NIO服务器启动,监听8080...");// 2️⃣ 创建 Selector(内核底层 epoll/kqueue)Selector selector = Selector.open();// 3️⃣ 将 ServerSocketChannel 注册到 selector,监听“连接就绪”事件serverChannel.register(selector, SelectionKey.OP_ACCEPT);// 4️⃣ 事件循环while (true) {System.out.println("等待事件发生...");// select() 会阻塞,直到至少有一个通道就绪selector.select(); // <-- 阻塞在多路复用器上(操作系统监视所有fd)// 5️⃣ 获取就绪事件集合Iterator<SelectionKey> iterator = selector.selectedKeys().iterator();while (iterator.hasNext()) {SelectionKey key = iterator.next();iterator.remove(); // 取出后要移除,避免重复处理// 6️⃣ 处理不同类型事件if (key.isAcceptable()) { // 新连接事件ServerSocketChannel ssc = (ServerSocketChannel) key.channel();SocketChannel client = ssc.accept(); // 取出新连接client.configureBlocking(false);System.out.println("新客户端连接:" + client.getRemoteAddress());client.register(selector, SelectionKey.OP_READ, ByteBuffer.allocate(1024));} else if (key.isReadable()) { // 可读事件SocketChannel client = (SocketChannel) key.channel();ByteBuffer buffer = (ByteBuffer) key.attachment();int len = client.read(buffer); // 非阻塞readif (len > 0) {buffer.flip();System.out.println("收到消息:" + new String(buffer.array(), 0, len));buffer.clear();} else if (len == -1) {System.out.println("客户端断开:" + client.getRemoteAddress());client.close();}}}}}
}内核层流程图解
初始化阶段
ServerSocketChannel.open() → syscall(socket)
Selector.open() → syscall(epoll_create)
register() → syscall(epoll_ctl(ADD, fd, event))事件循环阶段
selector.select() → syscall(epoll_wait)内核阻塞,等待任意fd状态变化(连接到达、可读、可写)== 事件触发 ==
内核检测到:- 某fd可读(TCP缓冲区有数据)- 某fd可写- 新连接到达监听fd== 内核通知用户态 ==
selector.select() 返回
→ Java线程被唤醒
→ 用户态开始处理事件内核交互图(NIO版)
用户线程(Java层) 操作系统(内核态)
----------------------------------------------------
register() ------------> epoll_ctl(ADD)内核记录:此fd要监听哪些事件select() ------------> epoll_wait()等待任何一个fd状态变化若事件发生 → 唤醒线程read() ------------> 非阻塞调用,若无数据立即返回0若有数据 → 内核拷贝 → 用户空间返回精华对比总结
对比点 | BIO | NIO |
编程接口 | InputStream / OutputStream | Channel / Buffer |
阻塞模型 | 调用 | 非阻塞,立即返回 |
并发模型 | 一连接一线程 | 一个线程监听所有连接 |
多路复用 | ❌ 无 | ✅ 由 Selector + epoll 实现 |
select() 是否阻塞 | ✅ 是,但只阻塞在等待事件阶段(而非数据读取阶段) | |
数据读取 | 内核 → 用户缓冲区 | 通道对应独立缓冲区(ByteBuffer) |
缓冲区积压 | 每个通道独立维护,受内核 TCP buffer 限制 | 控制得当不会溢出(epoll 水平触发或边缘触发控制) |
操作系统交互 | 每次I/O调用都会syscall | 注册后由epoll_wait监控fd状态 |
AIO
一、AIO(Asynchronous I/O)核心思想
AIO 是 Java 7 (java.nio.channels 包) 引入的真正的异步非阻塞 I/O 模型。
不同于 NIO 的“一个线程轮询多个通道”,AIO 是由操作系统完成 I/O 并主动通知应用线程。
即:
- 发起请求 → 操作系统后台执行 → I/O 完成后回调用户定义的处理逻辑(
CompletionHandler)。
底层机制(重点说明给面试官听)
在 Linux 下,Java 的 AIO 实际上是基于底层的 epoll + 线程池 模拟异步,而在 Windows 上是基于系统的真正异步 I/O API(如 IOCP)。
所以你可以总结:
“AIO 是真正的异步模型,数据读写都由内核完成后再通知应用层回调。相比之下,NIO 只是多路复用,线程仍需主动去取数据。”
二、AIO 经典实现代码示例
这是最常见的 AIO 服务端和客户端实现(基于 AsynchronousServerSocketChannel 与 AsynchronousSocketChannel)👇
服务端示例:异步接受客户端请求并读取数据
import java.net.InetSocketAddress;
import java.nio.ByteBuffer;
import java.nio.channels.AsynchronousServerSocketChannel;
import java.nio.channels.AsynchronousSocketChannel;
import java.nio.channels.CompletionHandler;public class AioServer {public static void main(String[] args) throws Exception {// 1. 打开异步 Server 通道AsynchronousServerSocketChannel server =AsynchronousServerSocketChannel.open().bind(new InetSocketAddress(8888));System.out.println("AIO Server started on port 8888...");// 2. 异步等待客户端连接server.accept(null, new CompletionHandler<AsynchronousSocketChannel, Void>() {@Overridepublic void completed(AsynchronousSocketChannel client, Void attachment) {// 继续接受下一个连接(关键)server.accept(null, this);ByteBuffer buffer = ByteBuffer.allocate(1024);// 3. 异步读取客户端数据client.read(buffer, buffer, new CompletionHandler<Integer, ByteBuffer>() {@Overridepublic void completed(Integer result, ByteBuffer buf) {buf.flip();String msg = new String(buf.array(), 0, result);System.out.println("Received: " + msg);// 4. 异步写回响应ByteBuffer response = ByteBuffer.wrap(("Echo: " + msg).getBytes());client.write(response);}@Overridepublic void failed(Throwable exc, ByteBuffer buf) {exc.printStackTrace();}});}@Overridepublic void failed(Throwable exc, Void attachment) {exc.printStackTrace();}});// 主线程阻塞(防止退出)Thread.currentThread().join();}
}客户端示例
import java.net.InetSocketAddress;
import java.nio.ByteBuffer;
import java.nio.channels.AsynchronousSocketChannel;
import java.nio.channels.CompletionHandler;public class AioClient {public static void main(String[] args) throws Exception {AsynchronousSocketChannel client = AsynchronousSocketChannel.open();client.connect(new InetSocketAddress("127.0.0.1", 8888), null,new CompletionHandler<Void, Void>() {@Overridepublic void completed(Void result, Void attachment) {ByteBuffer buffer = ByteBuffer.wrap("Hello AIO".getBytes());client.write(buffer);ByteBuffer readBuffer = ByteBuffer.allocate(1024);client.read(readBuffer, readBuffer, new CompletionHandler<Integer, ByteBuffer>() {@Overridepublic void completed(Integer result, ByteBuffer buf) {buf.flip();System.out.println("Server replied: " +new String(buf.array(), 0, result));}@Overridepublic void failed(Throwable exc, ByteBuffer buf) {exc.printStackTrace();}});}@Overridepublic void failed(Throwable exc, Void attachment) {exc.printStackTrace();}});Thread.sleep(3000);}
}三、AIO vs NIO 对比(重点总结)
对比项 | BIO | NIO | AIO |
模型类型 | 同步阻塞 | 同步非阻塞 | 异步非阻塞 |
I/O 调用 | 阻塞调用 | Selector 轮询 | 回调通知 |
底层机制 | 每连接一线程 | I/O 多路复用(epoll/select) | 内核完成后回调(IOCP/模拟异步) |
线程模型 | 一个连接一个线程 | 一个线程可管理多个连接 | 线程发起请求后立刻返回,由回调处理 |
适用场景 | 连接少、简单业务 | 高并发、短连接场景(如聊天) | 超高并发、I/O 密集、任务耗时较长场景 |
Java 包 | java.io | java.nio | java.nio.channels(NIO2) |
