零拷贝在 Java 中的具体 API | LIXI.FUN
0%

零拷贝在 Java 中的具体 API

这里不再赘述 零拷贝 定义的相关内容,感兴趣可以查看 维基百科 - 零拷贝

Java 原生

在 Java 中,零拷贝具体的 API 指的是

1
2
3
4
java.nio.channels

FileChannel#transferTo(long position, long count, WritableByteChannel target);
FileChannel#transferFrom(ReadableByteChannel src, long position, long count)

其中的 WritableByteChannelReadableByteChannel 可以使用 java.nio.channels.Channels#newChannel注意这里是 Channels 后面有 s 是工具类) 从 OutputStreamInputStream 中获取。

Netty 中使用

1
2
3
4
// 接口
io.netty.channel.FileRegion
// 实现,把原生 FileChannel 包装下使用
io.netty.channel.DefaultFileRegion

Handler 直接 write 出去 FileRegion 类型的对象就可以,netty 会主动判断类型,如果是 FeilRegion 会做相应处理。

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
io.netty.channel.nio.AbstractNioByteChannel#

doWriteInternal(ChannelOutboundBuffer in, Object msg) {

if (msg instanceof ByteBuf) {
// ...
} else if (msg instanceof FileRegion) {

FileRegion region = (FileRegion) msg;
if (region.transferred() >= region.count()) {
in.remove();
return 0;
}

// 这个地方调用的 doWriteFileRegion 就是了
long localFlushedAmount = doWriteFileRegion(region);
if (localFlushedAmount > 0) {
in.progress(localFlushedAmount);
if (region.transferred() >= region.count()) {
in.remove();
}
return 1;
}

} else {
// Should not reach here.
throw new Error();
}
return WRITE_STATUS_SNDBUF_FULL;
}

// 最终调用到
io.netty.channel.socket.nio.NioSocketChannel#

doWriteFileRegion(FileRegion region) {
final long position = region.transferred();
// 这里的 transferTo 就是了
return region.transferTo(javaChannel(), position);
}

可点击查看 Netty 关于 FileRegion 的详细文档。

问题

  1. Windows 下大文件,效果可能不佳,所以在 windows 上写 demo 程序可能得出截然相反的结果。

  2. Windows 下直接使用 FileChannel 传输大于 8M 的文件,需要多次传输。

    • Linux 中无此问题
    • 如果直接使用 netty 中的 FileRegion 无需关心此问题,其通过 transferred 字段记录了 position 位置。

windows 中使用原生 Channel 记录传输位置的方案

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
/**
* Transfers bytes from origin channel to the given writable byte
* channel.
*
* @param origin The origin channel
* @param target The target channel
* @return amount The number of bytes that were actually transferred
* @throws IOException If some I/O error occurs
*/
public static long transferTo(FileChannel origin, WritableByteChannel target)
throws IOException {

long size = origin.size();
long transferred = 0;

while (transferred < size) {
transferred += origin.transferTo(transferred, size, target);
}

return transferred;
}
觉得有收获就鼓励下作者吧