个人网站

面试官问"IO 和 NIO 的区别",很多人只能说出"IO 是阻塞的,NIO 是非阻塞的"。但追问"阻塞到底是什么意思"、“NIO 的 Selector 怎么实现非阻塞”、“NIO 就一定比 IO 快吗”,就答不全了。

今天咱们把 IO 和 NIO 的区别从模型到实现彻底讲透。

一、先说结论:IO vs NIO 核心区别

维度 IO(传统 IO) NIO(New IO)
模型 阻塞 IO(Blocking IO) 非阻塞 IO + 多路复用
数据操作 流(Stream)单向 缓冲区(Buffer)双向
核心组件 Stream Channel + Buffer + Selector
线程模型 一连接一线程 一线程处理多连接
适用场景 连接少且固定 连接多且短(高并发)
零拷贝 ✅ transferTo

一句话记住:IO 像"一对一客服"——一个客户占一个客服;NIO 像"叫号系统"——一个客服同时看多个号,谁准备好了叫谁。

二、阻塞 IO:一连接一线程

传统 IO 的工作方式:

// 服务端:一个连接需要一个线程
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();
}

问题:

1000 个连接 = 1000 个线程
├── 线程创建/销毁开销大
├── 线程上下文切换开销大
├── 每个线程占用约 1MB 栈空间 → 1000 线程 = 1GB 👈
└── 大量连接空闲时,线程白白阻塞浪费资源

生活类比: 阻塞 IO 像银行柜台——一个柜员只服务一个客户,即使客户在填表(不说话),柜员也等着,不能服务别人。

三、NIO 三大核心组件

Channel(通道)

双向通道,可以同时读写。 和 Stream 的关键区别:

特性 Stream Channel
方向 单向(InputStream 或 OutputStream) 双向(读写一体)
阻塞 只支持阻塞 支持阻塞和非阻塞
与 Buffer 配合 直接操作 byte[] 必须通过 Buffer
// Channel 读写都要通过 Buffer
FileChannel channel = FileChannel.open(path);
ByteBuffer buf = ByteBuffer.allocate(1024);
channel.read(buf);   // Channel → Buffer
buf.flip();
channel.write(buf);  // Buffer → Channel

Buffer(缓冲区)

一块可读可写的内存区域,NIO 所有操作都通过 Buffer。

ByteBuffer buf = ByteBuffer.allocate(1024);  // 分配 1024 字节

// 写入 Buffer
buf.put((byte) 1);
buf.put((byte) 2);

// 切换为读模式 👈
buf.flip();

// 读取 Buffer
byte b1 = buf.get();  // 1
byte b2 = buf.get();  // 2

// 清空 Buffer
buf.clear();

Buffer 的核心属性:

0 <= mark <= position <= limit <= capacity

capacity ── Buffer 总容量(不可变)
limit    ── 第一个不可读/写的位置
position ── 当前读/写位置
mark     ── 标记位置,可 reset 回来

flip() 的本质: limit = position; position = 0;——把"写完了多少"变成"能读多少"。

Selector(选择器)

NIO 的核心——多路复用器,一个线程监控多个 Channel。

Selector selector = Selector.open();

// 注册 Channel 到 Selector,监听读事件
channel.configureBlocking(false);  // 必须非阻塞 👈
channel.register(selector, SelectionKey.OP_READ);

// 轮询就绪的 Channel
while (true) {
    int readyCount = selector.select();  // 👈 阻塞直到有 Channel 就绪
    Set<SelectionKey> keys = selector.selectedKeys();
    for (SelectionKey key : keys) {
        if (key.isReadable()) {
            // 处理读事件
        }
    }
}

一个线程管理多个连接——这是 NIO 的核心优势。

四、NIO 的线程模型对比

传统 IO:
Client1 → Thread1(阻塞)
Client2 → Thread2(阻塞)
Client3 → Thread3(阻塞)
... 1000 个连接 → 1000 个线程 👈

NIO:
Client1 ─┐
Client2 ─┼→ Selector → 1 个线程处理所有就绪事件 👈
Client3 ─┘

NIO 的优势在高并发:

连接数 IO 线程数 NIO 线程数
10 10 1
1000 1000 1~4
10000 💥 OOM 1~4

但 NIO 就一定比 IO 快吗? 不一定!

场景 推荐 原因
连接少、数据量大 IO 阻塞模型简单高效
连接多、数据量小 NIO 多路复用省线程
文件操作 NIO 零拷贝、内存映射

生活类比: IO 像"专车"——一对一服务,舒适但贵;NIO 像"公交"——一对多,便宜但需要等站。

五、NIO 的编程复杂度

NIO 的缺点:编程复杂度高。

// 传统 IO:5 行搞定
BufferedReader reader = new BufferedReader(new FileReader("file.txt"));
String line;
while ((line = reader.readLine()) != null) {
    System.out.println(line);
}

// NIO:需要处理 Buffer 的 flip/clear/compact
FileChannel channel = FileChannel.open(path);
ByteBuffer buf = ByteBuffer.allocate(1024);
while (channel.read(buf) != -1) {
    buf.flip();           // 切读模式
    while (buf.hasRemaining()) {
        System.out.print((char) buf.get());
    }
    buf.clear();          // 清空
}

Netty 的价值: 封装了 NIO 的复杂性,提供简单易用的 API——大部分 Java 网络应用都用 Netty 而非直接用 NIO。

IO vs NIO 全景

IO vs NIO 全景

核心区别
├── 模型 ── 阻塞 vs 非阻塞+多路复用
├── 操作单位 ── Stream vs Buffer
├── 方向 ── 单向 vs 双向
├── 线程模型 ── 一连接一线程 vs 一线程多连接
└── 零拷贝 ── ❌ vs ✅

NIO 三大组件
├── Channel ── 双向通道
├── Buffer ── 读写缓冲区(flip/clear)
└── Selector ── 多路复用器

适用场景
├── 连接少数据大 → IO(简单高效)
├── 连接多数据小 → NIO(省线程)
└── 文件操作 → NIO(零拷贝)

NIO 的缺点
├── 编程复杂 ── Buffer 的 flip/clear
├── 断线重连 ── 需要自己处理
└── 半包粘包 ── 需要自己处理
└── 解决方案 → Netty

口诀:IO 阻塞一线程,NIO 多路复用省,
      Channel Buffer 和 Selector,三个组件是核心,
      连接多用 NIO,连接少用传统 IO,
      NIO 复杂用 Netty,选对模型效率高。

回答技巧与点评

标准回答

IO 和 NIO 的核心区别有三:第一,IO 是阻塞模型,一个连接需要一个线程,NIO 是非阻塞+多路复用模型,一个线程可以处理多个连接;第二,IO 基于流(Stream)操作,单向且直接操作 byte,NIO 基于缓冲区(Buffer)操作,双向且必须通过 Channel 和 Buffer;第三,NIO 支持 Selector 多路复用和零拷贝(transferTo),IO 不支持。选择时,连接多且短的场景用 NIO,连接少且数据量大的场景用传统 IO 即可。

加分回答
  1. Reactor 模式:NIO 的 Selector 多路复用本质上是 Reactor 模式的实现——Selector 是 Reactor(反应器),负责监听事件并分发,Channel 是资源,Handler 处理具体业务。Netty 的 EventLoop 就是 Reactor 模式的工程化实现,支持主从 Reactor(boss group + worker group)
  2. AIO(异步 IO):Java 7 引入了 NIO.2/AIO(AsynchronousChannel),是真正的异步 IO——发起 IO 请求后立即返回,操作系统完成后回调通知。NIO 是"非阻塞 IO",仍然需要线程去轮询就绪事件;AIO 是"异步 IO",完全不需要线程等。但 Linux 对 AIO 支持不完善,Netty 也放弃了 AIO 转而使用 NIO
  3. epoll 的水平触发和边缘触发:Linux 的 epoll 是 Selector 的底层实现。水平触发(LT)——只要缓冲区有数据就通知;边缘触发(ET)——只在缓冲区从空变为非空时通知一次。Java NIO 的 Selector 使用水平触发,而 Nginx 和 Redis 使用边缘触发——边缘触发性能更高但编程更复杂
面试官点评

这道题考的是你对 IO 模型的理解。能说出"阻塞 vs 非阻塞、Stream vs Buffer、Selector 多路复用"是基本要求,能讲清楚 NIO 的三大组件、各自的适用场景、NIO 不一定比 IO 快,才算及格。如果你能提到 Reactor 模式、AIO vs NIO 的区别、epoll 的触发模式,面试官会认为你对 IO 模型的理解已经深入到了操作系统和设计模式层面。

原文阅读


内容有帮助?点赞、收藏、关注三连!评论区等你 💪

Logo

AtomGit 是由开放原子开源基金会联合 CSDN 等生态伙伴共同推出的新一代开源与人工智能协作平台。平台坚持“开放、中立、公益”的理念,把代码托管、模型共享、数据集托管、智能体开发体验和算力服务整合在一起,为开发者提供从开发、训练到部署的一站式体验。

更多推荐