通过 transferTo() 将 fromChannel 中的数据拷贝到 toChannel:
- @Test
- public void transferTo() throws Exception {
- try (FileChannel fromChannel = new RandomAccessFile(
- getClassPath(SOURCE_FILE), "rw").getChannel();
- FileChannel toChannel = new RandomAccessFile(
- getClassPath(TARGET_FILE), "rw").getChannel()) {
- long position = 0L;
- long offset = fromChannel.size();
- fromChannel.transferTo(position, offset, toChannel);
- }
- }
- @Test
- public void transferFrom() throws Exception {
- try (FileChannel fromChannel = new RandomAccessFile(
- getClassPath(SOURCE_FILE), "rw").getChannel();
- FileChannel toChannel = new RandomAccessFile(
- getClassPath(TARGET_FILE), "rw").getChannel()) {
- long position = 0L;
- long offset = fromChannel.size();
- toChannel.transferFrom(fromChannel, position, offset);
- }
- }
transferTo() 和 transferFrom() 底层都是基于 Sendfile 实现数据传输的,其中 FileChannelImpl.java 定义了 3 个常量,用于标示当前操作系统的内核是否支持 Sendfile 以及 Sendfile 的相关特性 。
- private static volatile boolean transferSupported = true;
- private static volatile boolean pipeSupported = true;
- private static volatile boolean fileSupported = true;
pipeSupported:用于标记当前的系统内核是否支持文件描述符(fd)基于管道(pipe)的 sendfile() 调用,默认为 true 。
fileSupported:用于标记当前的系统内核是否支持文件描述符(fd)基于文件(file)的 sendfile() 调用,默认为 true 。
下面以 transferTo() 的源码实现为例 。FileChannelImpl 首先执行 transferToDirectly() 方法,以 Sendfile 的零拷贝方式尝试数据拷贝 。
如果系统内核不支持 Sendfile,进一步执行 transferToTrustedChannel() 方法,以 mmap 的零拷贝方式进行内存映射,这种情况下目的通道必须是 FileChannelImpl 或者 SelChImpl 类型 。
如果以上两步都失败了,则执行 transferToArbitraryChannel() 方法,基于传统的 I/O 方式完成读写,具体步骤是初始化一个临时的 DirectBuffer,将源通道 FileChannel 的数据读取到 DirectBuffer,再写入目的通道 WritableByteChannel 里面 。
- public long transferTo(long position, long count, WritableByteChannel target)
- throws IOException {
- // 计算文件的大小
- long sz = size();
- // 校验起始位置
- if (position > sz)
- return 0;
- int icount = (int)Math.min(count, Integer.MAX_VALUE);
- // 校验偏移量
- if ((sz - position) < icount)
- icount = (int)(sz - position);
- long n;
- if ((n = transferToDirectly(position, icount, target)) >= 0)
- return n;
- if ((n = transferToTrustedChannel(position, icount, target)) >= 0)
- return n;
- return transferToArbitraryChannel(position, icount, target);
- }
可以看到,transferToDirectlyInternal() 方法先获取到目的通道 WritableByteChannel 的文件描述符 targetFD,获取同步锁然后执行 transferToDirectlyInternal() 方法 。
- private long transferToDirectly(long position, int icount, WritableByteChannel target)
- throws IOException {
- // 省略从target获取targetFD的过程
- if (nd.transferToDirectlyNeedsPositionLock()) {
- synchronized (positionLock) {
- long pos = position();
- try {
- return transferToDirectlyInternal(position, icount,
- target, targetFD);
- } finally {
- position(pos);
- }
- }
- } else {
- return transferToDirectlyInternal(position, icount, target, targetFD);
- }
- }
如果系统内核完全不支持 Sendfile,比如 Windows 操作系统,则返回 UNSUPPORTED 并把 transferSupported 标识为 false 。
如果系统内核不支持 Sendfile 的一些特性,比如说低版本的 Linux 内核不支持 DMA gather copy 操作,则返回 UNSUPPORTED_CASE 并把 pipeSupported 或者 fileSupported 标识为 false 。
- private long transferToDirectlyInternal(long position, int icount,
推荐阅读
- 淘宝从百万到千万级并发的14次服务端架构演进之路
- 几百万扔进水?买二手房千万避开这几类房源
- 分布式、高并发、多线程,这些概念还傻傻分不清吗?
- Java 并发编程:如何保证共享变量的原子性?
- 硬核!如何模拟 5w+ 的并发用户?
- 格鲁吉亚中国茶王刘峻周诞辰150周年之际获百万赔偿
- 安徽省科技厅强化科技支撑推进精准扶贫
- PHP导出百万条数据方法
- 重金寻人 , 你是我们要找的百万英雄吗 内含福利
- 带你深入了解高并发架构
