Java NIO 中的通道机制详解
通道的基本概念
在 Java NIO 框架中,通道(Channel)是数据传输的核心组件,类似于物理世界中的管道或铁轨。它本身不存储数据,而是作为连接数据源与缓冲区之间的桥梁,实现高效的数据读写操作。通道位于 java.nio.channels 包中,支持文件、网络套接字等多种 I/O 实体,并为非阻塞和多路复用 I/O 提供基础。
常见的通道实现包括:
- FileChannel:用于对文件进行读取、写入、映射等操作。
- DatagramChannel:支持 UDP 协议的数据报通信。
- SocketChannel:面向 TCP 连接的客户端通道。
- ServerSocketChannel:用于监听 TCP 连接请求的服务端通道。
获取通道的方式
Java 提供了多种途径来获取通道实例:
- 通过支持通道的类调用其
getChannel()方法:- 本地文件操作:如
FileInputStream、FileOutputStream和RandomAccessFile。 - 网络通信类:如
Socket、ServerSocket和DatagramSocket。
- 本地文件操作:如
- JDK 7 引入的
Files工具类提供了静态方法newByteChannel(),可直接打开一个通道。 - 使用
FileChannel.open()静态方法,传入路径和打开选项(如读、写、创建)来获取通道。
文件复制示例
以下示例将 D 盘下的 test.txt 复制到 E 盘并重命名为 copy.txt。
1. 使用堆内存缓冲区(非直接缓冲区)
@Test
public void copyWithHeapBuffer() throws IOException {
FileInputStream in = new FileInputStream("D:" + File.separator + "test.txt");
FileOutputStream out = new FileOutputStream("E:" + File.separator + "copy.txt");
FileChannel inChannel = in.getChannel();
FileChannel outChannel = out.getChannel();
ByteBuffer buffer = ByteBuffer.allocate(1024); // 堆内缓冲区
while (inChannel.read(buffer) != -1) {
buffer.flip(); // 切换至读模式
outChannel.write(buffer);
buffer.clear(); // 清空以便下次读取
}
inChannel.close();
outChannel.close();
in.close();
out.close();
}
2. 使用内存映射缓冲区(直接缓冲区)
@Test
public void copyWithMemoryMap() throws IOException {
Path inputPath = Paths.get("D:", "test.txt");
Path outputPath = Paths.get("E:", "copy.txt");
FileChannel inChannel = FileChannel.open(inputPath, StandardOpenOption.READ);
FileChannel outChannel = FileChannel.open(outputPath,
StandardOpenOption.WRITE,
StandardOpenOption.CREATE_NEW,
StandardOpenOption.READ);
// 映射整个文件到内存
MappedByteBuffer srcBuffer = inChannel.map(MapMode.READ_ONLY, 0, inChannel.size());
MappedByteBuffer dstBuffer = outChannel.map(MapMode.READ_WRITE, 0, inChannel.size());
byte[] data = new byte[srcBuffer.remaining()];
srcBuffer.get(data);
dstBuffer.put(data);
inChannel.close();
outChannel.close();
}
3. 通道间直接传输(零拷贝优化)
@Test
public void transferBetweenChannels() throws IOException {
try (FileChannel source = FileChannel.open(Paths.get("D:", "test.txt"), StandardOpenOption.READ);
FileChannel target = FileChannel.open(Paths.get("E:", "copy.txt"),
StandardOpenOption.WRITE,
StandardOpenOption.CREATE_NEW)) {
// 方式一:从源通道推送数据到目标通道
// source.transferTo(0, source.size(), target);
// 方式二:目标通道从源拉取数据(推荐)
target.transferFrom(source, 0, source.size());
}
}
上述方法利用操作系统级别的零拷贝机制,避免了用户空间与内核空间之间的多次数据复制,显著提升大文件处理性能。
分散读取与聚集写入
NIO 支持将单个通道的数据分散写入多个缓冲区(Scatter Reads),或将多个缓冲区的数据集中写入一个通道(Gathering Writes),适用于消息头+消息体等结构化数据处理场景。
代码演示
@Test
public void scatterAndGather() throws IOException {
// 打开源文件进行读写
RandomAccessFile sourceFile = new RandomAccessFile("D:" + File.separator + "testFile.txt", "rw");
FileChannel channel = sourceFile.getChannel();
ByteBuffer header = ByteBuffer.allocate(100);
ByteBuffer body = ByteBuffer.allocate(1024);
ByteBuffer[] buffers = {header, body};
// 分散读取:按顺序填充每个缓冲区
channel.read(buffers);
// 切换所有缓冲区为读模式
for (ByteBuffer buf : buffers) {
buf.flip();
}
// 查看分散结果
System.out.println("Header: " + new String(header.array(), 0, header.limit()));
System.out.println("Body: " + new String(body.array(), 0, body.limit()));
// 聚集写入:将多个缓冲区内容顺序写入目标通道
RandomAccessFile destFile = new RandomAccessFile("E:" + File.separator + "copyFile.txt", "rw");
FileChannel outputChannel = destFile.getChannel();
outputChannel.write(buffers); // 按数组顺序写出
channel.close();
outputChannel.close();
}
该机制特别适合解析固定格式的二进制协议,例如先读取头部长度字段再读取具体内容。