本文目录导读:

- 网络通信类案例(核心:Selector + Channel)
- 文件操作类案例(核心:FileChannel + 零拷贝)
- 综合案例:结合 NIO + 多线程的 HTTP Server
- 总结:NIO 核心知识点在案例中的体现
- 学习建议
Java NIO(New I/O,即 Java 1.4 引入的非阻塞 I/O 模型)的核心在于通道(Channel)、缓冲区(Buffer) 和选择器(Selector),它特别适合处理高并发、大文件传输和网络通信场景。
以下按网络通信和文件操作两大类,给出几个经典的 Java NIO 案例,并附上核心代码逻辑说明。
网络通信类案例(核心:Selector + Channel)
这类案例是 NIO 最典型的应用,用于替代传统的 BIO(Blocking I/O)连接池模型。
简易多人聊天室(核心演示:非阻塞 + Selector)
功能:一个服务端同时处理多个客户端连接,客户端可以发送消息,服务端广播给所有人。
核心思想:
- 服务端使用
Selector监听OP_ACCEPT(新连接)和OP_READ(数据到达)事件。 - 所有客户端连接都注册到同一个
Selector上,单线程即可处理大量连接。
关键代码片段(服务端):
import java.io.IOException;
import java.net.InetSocketAddress;
import java.nio.ByteBuffer;
import java.nio.channels.*;
import java.util.Iterator;
public class NioChatServer {
private Selector selector;
private ServerSocketChannel serverSocketChannel;
public void start() throws IOException {
// 1. 打开 ServerSocketChannel 并绑定端口
serverSocketChannel = ServerSocketChannel.open();
serverSocketChannel.socket().bind(new InetSocketAddress(8080));
serverSocketChannel.configureBlocking(false); // 非阻塞模式
// 2. 打开 Selector 并注册 ACCEPT 事件
selector = Selector.open();
serverSocketChannel.register(selector, SelectionKey.OP_ACCEPT);
System.out.println("NIO 聊天服务器启动,端口:8080");
while (true) {
// 3. 选择就绪的事件(阻塞等待,直到有事件发生)
selector.select();
Iterator<SelectionKey> keyIterator = selector.selectedKeys().iterator();
while (keyIterator.hasNext()) {
SelectionKey key = keyIterator.next();
keyIterator.remove(); // 必须移除,否则会重复处理
if (key.isAcceptable()) {
handleAccept(key);
} else if (key.isReadable()) {
handleRead(key);
}
}
}
}
private void handleAccept(SelectionKey key) throws IOException {
ServerSocketChannel server = (ServerSocketChannel) key.channel();
SocketChannel client = server.accept();
client.configureBlocking(false);
// 将新客户端注册到 Selector,监听 READ 事件
client.register(selector, SelectionKey.OP_READ);
System.out.println("新客户端连接:" + client.getRemoteAddress());
}
private void handleRead(SelectionKey key) throws IOException {
SocketChannel client = (SocketChannel) key.channel();
ByteBuffer buffer = ByteBuffer.allocate(1024);
int bytesRead = client.read(buffer);
if (bytesRead > 0) {
buffer.flip();
byte[] data = new byte[buffer.limit()];
buffer.get(data);
String msg = new String(data);
System.out.println("收到消息:" + msg);
// 广播给其他所有客户端
broadcast(client, msg);
} else {
// 客户端断开连接
client.close();
System.out.println("客户端断开");
}
}
private void broadcast(SocketChannel excludeClient, String msg) throws IOException {
ByteBuffer buffer = ByteBuffer.wrap(msg.getBytes());
for (SelectionKey key : selector.keys()) {
Channel target = key.channel();
if (target instanceof SocketChannel && target != excludeClient) {
((SocketChannel) target).write(buffer);
buffer.rewind(); // 重置 position 以便再次写入
}
}
}
}
特点:单线程管理所有连接,适合连接数很大但活动连接较少的场景(如聊天、推送、网关)。
HTTP 代理服务器(实战:手写简单代理)
功能:接收客户端的 HTTP 请求,转发到目标服务器,再将响应返回给客户端,全部使用 NIO 实现。
核心:需要使用 Pipe 或 register 分别在两个 Channel 上监听读写事件,处理数据转发。
简化逻辑:
// 伪代码:通过两个 SelectionKey 管理 client 和 remote 之间的数据流转 SocketChannel clientChannel = ...; SocketChannel remoteChannel = ...; // 当 clientChannel 可读时,读取数据并写入 remoteChannel // 当 remoteChannel 可读时,读取数据并写入 clientChannel // 这两个读写动作都注册在同一个 Selector 上
文件操作类案例(核心:FileChannel + 零拷贝)
文件拷贝(MappedByteBuffer 大文件高效拷贝)
功能:使用 FileChannel.map() 将文件直接映射到内存(MappedByteBuffer),实现零拷贝(用户态与内核态之间数据不经过 JVM 堆),速度远快于传统 InputStream/OutputStream。
代码:
import java.io.RandomAccessFile;
import java.nio.MappedByteBuffer;
import java.nio.channels.FileChannel;
public class FileCopyWithMappedByteBuffer {
public static void copyFile(String src, String dest) throws Exception {
RandomAccessFile srcFile = new RandomAccessFile(src, "r");
RandomAccessFile destFile = new RandomAccessFile(dest, "rw");
FileChannel srcChannel = srcFile.getChannel();
FileChannel destChannel = destFile.getChannel();
long fileSize = srcChannel.size();
// 将源文件映射到内存
MappedByteBuffer mappedByteBuffer = srcChannel.map(FileChannel.MapMode.READ_ONLY, 0, fileSize);
// 写入目标文件
destChannel.write(mappedByteBuffer);
srcChannel.close();
destChannel.close();
srcFile.close();
destFile.close();
System.out.println("文件拷贝完成(使用 MappedByteBuffer)");
}
}
对比传统方式:直接 transferTo() 或 transferFrom() 方法性能更高(也是零拷贝),但 MappedByteBuffer 可以让你在内存中直接操作文件内容。
文件分片传输(Chunked File Transfer)
案例背景:网络编程中,一个大的文件需要分成多个小块发送,或者从多个位置并发读取,NIO 的 FileChannel 支持位置 (position) 指定。
代码:
import java.io.RandomAccessFile;
import java.nio.ByteBuffer;
import java.nio.channels.FileChannel;
public class FileChunkReader {
// 读取文件的指定片段
public static byte[] readChunk(String path, long offset, int length) throws Exception {
RandomAccessFile file = new RandomAccessFile(path, "r");
FileChannel channel = file.getChannel();
ByteBuffer buffer = ByteBuffer.allocate(length);
// 关键:从指定位置开始读取
channel.position(offset);
channel.read(buffer);
buffer.flip();
byte[] data = new byte[buffer.limit()];
buffer.get(data);
channel.close();
file.close();
return data;
}
}
实际应用:断点续传、分片下载、日志分段分析等。
非阻塞 Socket 读取文件示例(结合网络传输)
场景:客户端请求下载文件,服务端使用非阻塞 SocketChannel + FileChannel 发送文件内容。
关键点:FileChannel 本身是阻塞的(文件系统天生没有非阻塞),所以发送文件时通常要用一个线程池处理读文件,然后将数据写入 SocketChannel。
代码框架:
// 服务端写数据到 SocketChannel(需要确保数据发送缓冲区有空闲)
public class FileSender implements Runnable {
private SocketChannel socketChannel;
private FileChannel fileChannel;
// ... 初始化
@Override
public void run() {
ByteBuffer buffer = ByteBuffer.allocate(8192);
try {
while (fileChannel.read(buffer) != -1) {
buffer.flip();
socketChannel.write(buffer);
buffer.clear();
}
} catch (IOException e) {
e.printStackTrace();
}
}
}
但注意:真正的生产者-消费者模式中,通常配合 Selector 的写事件处理,避免持续占用 CPU。
综合案例:结合 NIO + 多线程的 HTTP Server
- 案例:基于 NIO 实现的简单 HTTP 解析 + 静态文件返回。
- 特点:
- 主线程只负责
Selector.select()分发事件。 - 读写操作交给线程池处理(避免阻塞主线程)。
- 解析 HTTP 请求头(Buffer 的
compact()和flip()操作)。 - 返回静态 HTML/图片文件(利用
FileChannel.transferTo()零拷贝发送)。
- 主线程只负责
这类案例常用于理解 Netty 底层设计思想。
NIO 核心知识点在案例中的体现
| 案例 | 核心 NIO 组件 | 典型应用场景 |
|---|---|---|
| 聊天室 | Selector, SocketChannel |
高并发长连接、事件驱动 |
| 代理服务器 | Selector, Pipe |
网关、中间件 |
| 大文件拷贝 | MappedByteBuffer, FileChannel |
文件传输、备份 |
| 分片读取 | FileChannel.position() |
断点续传、分片下载 |
| 非阻塞文件发送 | SocketChannel + FileChannel |
Web 服务器(NIO 版) |
学习建议
- 从文件操作入手:
FileChannel相对简单,无网络复杂性,容易理解 Buffer 和 Channel 的关系。 - 练习网络聊天室:这是理解 Selector 和事件驱动模型的最佳入门案例。
- 阅读 Netty 源码:Netty 是对 NIO 的封装,理解 NIO 后看 Netty 的 ChannelPipeline 和 EventLoop 会更容易。
如果你需要某个案例的完整可运行代码(如聊天室服务端 + 客户端),可以告诉我,我可以提供完整的类文件和测试方式。