Channel通道
Channel 是 NIO 中连接数据源和数据目标的通道。它和传统流类似,都用于读写数据,但 Channel 通常是双向的,并且和 Buffer 配合使用。
# 1. Channel 和 Stream 的区别
| 对比项 | Stream | Channel |
|---|---|---|
| 数据模型 | 面向流 | 面向缓冲区 |
| 方向 | 通常单向 | 通常双向 |
| 非阻塞 | 不支持或支持有限 | 网络 Channel 支持 |
| 配合对象 | byte[]、char[] | Buffer |
| 典型 API | InputStream、OutputStream | FileChannel、SocketChannel |
# 2. FileChannel
FileChannel 用于文件读写。
try (FileChannel channel = FileChannel.open(
Path.of("data.txt"),
StandardOpenOption.READ)) {
ByteBuffer buffer = ByteBuffer.allocate(1024);
int len = channel.read(buffer);
}
写入文件:
try (FileChannel channel = FileChannel.open(
Path.of("data.txt"),
StandardOpenOption.CREATE,
StandardOpenOption.WRITE,
StandardOpenOption.TRUNCATE_EXISTING)) {
ByteBuffer buffer = StandardCharsets.UTF_8.encode("hello nio");
while (buffer.hasRemaining()) {
channel.write(buffer);
}
}
注意 write 不保证一次写完 Buffer 中所有数据,所以要配合 while (buffer.hasRemaining())。
# 3. position
FileChannel 支持获取和设置当前位置。
long position = channel.position();
channel.position(100);
这使它可以实现随机访问文件。
也可以直接从指定位置读取:
channel.read(buffer, 1024);
这种方法不会改变 Channel 当前 position。
# 4. force
force 用于尽量把文件内容刷新到存储设备。
channel.force(true);
参数 true 表示同时刷新文件内容和元数据,false 通常只刷新内容。即便如此,实际语义仍受操作系统和存储设备影响。关键金融类数据要结合事务日志、校验、原子写入等机制设计,不能只依赖一次 force。
# 5. transferTo 和 transferFrom
FileChannel 支持高效数据传输:
try (FileChannel source = FileChannel.open(Path.of("source.dat"), StandardOpenOption.READ);
FileChannel target = FileChannel.open(Path.of("target.dat"),
StandardOpenOption.CREATE,
StandardOpenOption.WRITE,
StandardOpenOption.TRUNCATE_EXISTING)) {
long position = 0;
long size = source.size();
while (position < size) {
position += source.transferTo(position, size - position, target);
}
}
在某些操作系统上,这可以利用零拷贝能力,减少用户态和内核态之间的数据复制。
# 6. SocketChannel
SocketChannel 用于 TCP 客户端连接。
SocketChannel channel = SocketChannel.open();
channel.configureBlocking(false);
channel.connect(new InetSocketAddress("localhost", 8080));
网络 Channel 可以配置为非阻塞模式,并注册到 Selector。
# 7. ServerSocketChannel
ServerSocketChannel 用于 TCP 服务端监听。
ServerSocketChannel server = ServerSocketChannel.open();
server.bind(new InetSocketAddress(8080));
server.configureBlocking(false);
当有新连接到来时,Selector 可以收到 OP_ACCEPT 事件。
# 专家实践清单
| Channel | 场景 | 注意点 |
|---|---|---|
FileChannel | 文件读写、复制、映射 | 关注 position 和刷盘 |
SocketChannel | TCP 客户端连接 | 处理半包和半写 |
ServerSocketChannel | 接收连接 | 非阻塞模式配合 Selector |
DatagramChannel | UDP 通信 | 无连接、不保证可靠 |
AsynchronousFileChannel | 异步文件操作 | 回调和线程池管理 |
Channel 与 Buffer 的协作:
读:Channel -> Buffer -> 业务解析
写:业务编码 -> Buffer -> Channel
# Tips 快问快答
Q:Channel 是什么? A:NIO 中连接文件、网络等资源的数据通道。
Q:Channel 和 Buffer 谁保存数据? A:Buffer 保存数据,Channel 负责传输。
Q:Channel 是否都是双向的? A:很多 Channel 支持双向,但具体能力取决于实现类型。
Q:FileChannel 能做什么高级操作?
A:位置读写、文件映射、强制刷盘、transferTo/transferFrom。
Q:SocketChannel.write 一次一定写完吗?
A:不一定,非阻塞模式下可能只写出一部分。
Q:Channel 非阻塞模式有什么要求? A:需要配合 Selector 或事件循环处理就绪事件。
Q:为什么 FileChannel.force 重要?
A:它可请求把数据和元数据刷到底层存储,提高崩溃一致性。
Q:Channel 常见错误是什么? A:忽略部分读写、混淆 position、资源未关闭。
# 8. 总结
Channel 是 NIO 的数据通道,Buffer 是数据容器。文件场景中常用 FileChannel 实现随机访问、高效复制、强制刷盘;网络场景中常用 SocketChannel 和 ServerSocketChannel 结合 Selector 实现非阻塞通信。