Java I/O 模型经历了从 BIO(Blocking I/O)到 NIO(Non-blocking I/O / New I/O)的演进,其核心差异不仅体现在阻塞与非阻塞的行为上,更体现在数据交互的基本抽象单元------流(Stream)与通道(Channel)的设计哲学不同。
以下是对 NIO 与 BIO 的对比,以及 Stream 与 Channel 概念的深度剖析。
一、 NIO vs BIO:核心差异对比
BIO 是 Java 1.0 引入的传统同步阻塞 I/O 模型,而 NIO 自 JDK 1.4 引入,旨在解决高并发场景下的性能瓶颈。
| 特性 | BIO (Blocking I/O) | NIO (Non-blocking I/O) |
|---|---|---|
| 面向对象 | 面向流 (Stream Oriented) | 面向缓冲区 (Buffer Oriented) |
| 阻塞模式 | 同步阻塞:线程发起读写请求后,必须等待数据准备就绪或传输完成,期间线程无法做其他事情。 | 同步非阻塞:线程发起请求后立即返回,若数据未就绪,线程可处理其他任务;通过 Selector 轮询就绪事件。 |
| 并发模型 一对一:一个连接需要一个独立的线程处理。高并发下线程上下文切换开销巨大,资源消耗严重。 | 多路复用:一个线程(Selector)可以管理成千上万个连接。仅当连接真正有读写事件时才分配线程处理,极大减少线程数量。 | |
| 适用场景 | 连接数目少且固定,架构简单,对实时性要求不高。 | 连接数目多且连接较短(轻操作),如聊天服务器、HTTP 服务器、高性能网关。 |
| 底层实现 | 基于操作系统内核的阻塞式系统调用。 | 基于操作系统内核的多路复用机制(如 Linux 的 epoll, Windows 的 IOCP)。 |

二、 Stream(流) vs Channel(通道)概念剖析
BIO 和 NIO 最根本的区别在于数据传输的载体不同:BIO 使用 Stream,NIO 使用 Channel + Buffer。
1. Stream(流):单向、顺序的数据流
在 BIO 中,InputStream 和 OutputStream 是核心抽象。
单向性:流通常是单向的。你需要分别创建输入流和输出流来读取和写入数据。
直接操作字节:应用程序直接从流中读取字节或向流中写入字节。数据像水流一样,流过即消失,不支持随机访问(除非使用特殊的 RandomAccessFile,但其本质仍偏向流式思维)。
阻塞式:read() 方法会一直阻塞,直到有数据可读或到达流末尾。
局限性:流模型难以高效地支持非阻塞 I/O 和多路复用,因为线程必须"守"在流旁边等待数据。
2. Channel(通道):双向、基于缓冲区的连接
在 NIO 中,Channel 是核心抽象。通道表示到实体(如文件、网络套接字)的开放连接。
双向性:大多数 Channel(如 SocketChannel, FileChannel)既支持读也支持写。这使得通道可以更灵活地映射底层操作系统的本地代码。
基于缓冲区 (Buffer):这是 Channel 与 Stream 最大的区别。
你永远不能直接将字节写入 Channel,也不能直接从 Channel 读取字节。
所有数据都必须先读取到 ByteBuffer(缓冲区),或者从 ByteBuffer 写入到 Channel。
流程:Channel -> Buffer (读) 或 Buffer -> Channel (写)。
异步/非阻塞支持:Channel 可以配置为非阻塞模式 (configureBlocking(false))。在非阻塞模式下,读写操作会立即返回,可能没有读取到任何数据,需要检查返回值。
多路复用支持:Channel 可以注册到 Selector 上。Selector 能够监控多个 Channel 的状态(连接、接受、读、写),从而允许单线程管理多个连接。
- 为什么 NIO 要引入 Buffer?
批量操作:Buffer 允许一次性读取或写入一块数据,减少了系统调用的次数,提高了 I/O 效率。
内存映射:NIO 支持内存映射文件 (MappedByteBuffer),可以直接将文件的一部分映射到内存地址空间,极大地提高了大文件读写的性能,这是传统 Stream 难以高效实现的。
分散与聚集:NIO 提供了 ScatteringByteChannel 和 GatheringByteChannel,支持将数据读入多个缓冲区(分散)或从多个缓冲区写入数据(聚集),这对于处理具有固定头部和可变身体协议的网络数据包非常有用。
三、 总结
BIO (Stream) 像是水管:水(数据)只能单向流动,你必须一直开着水龙头等着水出来,如果没水,你就只能干等(阻塞)。每个水管都需要一个人(线程)守着。
NIO (Channel + Buffer) 像是快递中转站:
Channel 是传送带接口,它不直接处理包裹,而是连接到仓库(文件)或卡车(网络)。
Buffer 是集装箱/托盘。数据必须先装进托盘(Buffer)才能通过传送带(Channel)运输。
Selector 是调度员。他不需要守着每一个传送带,而是巡视所有传送带。只有当某个传送带上有货物准备好(事件就绪)时,调度员才通知工人(线程)去处理。
结论:在高并发、高性能要求的网络编程中,NIO 的 Channel/Buffer/Selector 模型因其非阻塞和多路复用的特性,取代了传统的 BIO Stream 模型,成为 Java 高性能网络框架(如 Netty, Mina, Tomcat NIO Connector)的基石