在传统 Java 开发中,我们熟悉的 I/O 操作通常依赖 java.io 包提供的流式 API,这类 API 以阻塞(Blocking)方式工作:线程在读取或写入数据时会被挂起,直到操作完成。这种方式在单线程或少量连接场景下工作良好,但面对高并发、大量连接或对性能敏感的场景,阻塞 I/O 很容易成为性能瓶颈。
为了解决这些问题,Java 在 1.4 版本引入了 NIO(New I/O,非阻塞 I/O)。NIO 提供了 通道(Channel)、缓冲区(Buffer)、选择器(Selector) 等核心概念,实现了非阻塞 I/O,极大提升了高并发场景下的 I/O 效率。
本文将详细解析 Java NIO 的核心概念、应用场景,并提供一个实战案例,帮助你深入理解并掌握 NIO。
缓冲区是 NIO 的数据容器,用于在通道中读写数据。核心特点:
capacity:缓冲区容量,固定大小。position:当前操作的位置(读或写)。limit:当前可以操作的最大位置。常用缓冲区:
ByteBuffer(字节缓冲区)CharBuffer(字符缓冲区)IntBuffer、DoubleBuffer 等示例:ByteBuffer 使用
ByteBuffer buffer = ByteBuffer.allocate(1024); // 分配1KB缓冲区
String data = "Hello NIO";
buffer.put(data.getBytes()); // 写入数据
buffer.flip(); // 切换到读取模式
byte[] bytes = new byte[buffer.remaining()];
buffer.get(bytes);
System.out.println(new String(bytes)); // 输出: Hello NIO
通道是 NIO 的数据传输媒介,它类似于传统 I/O 的流,但可以支持 非阻塞。
常用通道类型:
FileChannel:文件通道SocketChannel:TCP 网络通道ServerSocketChannel:TCP 服务端通道DatagramChannel:UDP 通道通道和缓冲区结合使用:
示例:FileChannel 写入文件
try (FileOutputStream fos = new FileOutputStream("nio.txt");
FileChannel channel = fos.getChannel()) {
String content = "Hello Java NIO";
ByteBuffer buffer = ByteBuffer.wrap(content.getBytes());
channel.write(buffer);
}
Selector 是 NIO 的核心组件,负责管理 多个通道的事件(OP_READ、OP_WRITE、OP_ACCEPT、OP_CONNECT),实现单线程处理多路 I/O。
优势:
Selector 使用流程:
select(),获取就绪事件| 特性 | 阻塞 IO (BIO) | 非阻塞 IO (NIO) |
|---|---|---|
| 模型 | 每个连接一个线程 | 单线程管理多连接 |
| 数据处理 | 阻塞读取/写入 | Buffer + Channel 非阻塞 |
| 适用场景 | 连接少、简单应用 | 高并发网络服务器 |
| 内存 | 直接使用数组 | ByteBuffer,支持直接缓冲区 (DirectBuffer) |
| 线程开销 | 高 | 低 |
NIO 特别适合:
下面我们用 NIO 实现一个简单的多客户端聊天服务器,特点:
import java.io.IOException;
import java.net.InetSocketAddress;
import java.nio.ByteBuffer;
import java.nio.channels.*;
import java.util.Iterator;
public class NioChatServer {
public static void main(String[] args) throws IOException {
Selector selector = Selector.open();
ServerSocketChannel serverChannel = ServerSocketChannel.open();
serverChannel.bind(new InetSocketAddress(9000));
serverChannel.configureBlocking(false);
serverChannel.register(selector, SelectionKey.OP_ACCEPT);
System.out.println("NIO 聊天服务器启动,端口 9000");
while (true) {
selector.select();
Iterator<SelectionKey> keyIterator = selector.selectedKeys().iterator();
while (keyIterator.hasNext()) {
SelectionKey key = keyIterator.next();
keyIterator.remove();
if (key.isAcceptable()) {
SocketChannel client = serverChannel.accept();
client.configureBlocking(false);
client.register(selector, SelectionKey.OP_READ);
System.out.println("新客户端连接:" + client.getRemoteAddress());
} else if (key.isReadable()) {
SocketChannel client = (SocketChannel) key.channel();
ByteBuffer buffer = ByteBuffer.allocate(1024);
int len = client.read(buffer);
if (len > 0) {
buffer.flip();
String msg = new String(buffer.array(), 0, len);
System.out.println("收到消息: " + msg.trim());
broadcast(selector, client, msg);
} else if (len == -1) {
System.out.println("客户端断开:" + client.getRemoteAddress());
key.cancel();
client.close();
}
}
}
}
}
private static void broadcast(Selector selector, SocketChannel sender, String msg) throws IOException {
for (SelectionKey key : selector.keys()) {
Channel target = key.channel();
if (target instanceof SocketChannel && target != sender) {
ByteBuffer buffer = ByteBuffer.wrap(msg.getBytes());
((SocketChannel) target).write(buffer);
}
}
}
}
客户端可用 telnet 127.0.0.1 9000 连接,然后发送消息即可看到广播效果。
ByteBuffer.allocateDirect(size) 在堆外内存操作,减少 JVM GC 影响FileChannel.transferTo 或 transferFrom 直接在内核空间拷贝数据Java NIO 为高性能 I/O 提供了非阻塞、可扩展的解决方案。通过 Buffer、Channel、Selector 组合使用,Java 开发者可以在单线程下高效处理上万并发连接,满足现代网络应用的高性能需求。
结合案例来看:
掌握 NIO,不仅能写出高性能服务器,还能深入理解 Java I/O 模型的演进与底层机制。




