附一段transforTo方法的doc:
This method is potentially much more efficient than a simple loop
that reads from this channel and writes to the target channel. Many operating systems can transfer bytes directly from the
filesystem cache to the target channel without actually copying them.
在oio和nio进行文件复制(zero-copy,直接从文件系统传输字节)效率对比的时候发现部分文件拷贝不全(以下只会展示client端代码);
public static void main(String[] args) throws Exception {
Socket socket = new Socket("localhost", 8899);
//文件4g+
String path = "E:\\CiKeXinTiao8AoDeSai\\DataPC_patch_01.forge";
String p = "E:\\ggg6.7z";
FileInputStream inputStream = new FileInputStream(p);
DataOutputStream dataOutputStream = new DataOutputStream(socket.getOutputStream());
byte[] bytes = new byte[4096];
long readCount;
long total = 0;
long startTime = System.currentTimeMillis();
while ((readCount = inputStream.read(bytes)) >= 0) {
System.out.println("写"+readCount+"个字节.....");
total += readCount;
dataOutputStream.write(bytes);
}
//发送总字节数:1727150186,耗时:9386 zero copy 1563
System.out.println("发送总字节数:" + total + "," + "耗时:" + (System.currentTimeMillis() - startTime));
socket.close();
}
SocketChannel socketChannel = SocketChannel.open();
//文件4g+
//String path = "E:\\CiKeXinTiao8AoDeSai\\DataPC_patch_01.forge";
//270m+
String path = "E:\\CiKeXinTiao8AoDeSai\\ACOdyssey.exe";
String p = "E:\\ggg6.7z";
socketChannel.connect(new InetSocketAddress("localhost", 8900));
socketChannel.configureBlocking(true);
FileChannel fileChannel = new FileInputStream(p).getChannel();
long currentCount = fileChannel.transferTo(0, size, socketChannel);
直接使用FileChannel的transferTo向sockerChannel复制数据,这时会发现,发送字节数并不等于文件总字节数。
long startTime = System.currentTimeMillis();
long size = fileChannel.size();
long position = 0;
long total = 0;
while (position < size) {
long currentNum = fileChannel.transferTo(position, fileChannel.size(), socketChannel);
System.out.println("复制字节数:"+currentNum);
if (currentNum <= 0) {
break;
}
total += currentNum;
position += currentNum;
}
System.out.println("发送总字节数:" + total + " 耗时:" + (System.currentTimeMillis() - startTime));
这时是可以全部复制完成的。
long currentCount = fileChannel.transferTo(0, size, socketChannel);
改为
long currentCount = fileChannel.transferTo(0, size, new FileOutputStream(p).getChannel());
此时发现,小于2g是可以一次复制完的。
all of the requested bytes; whether or not it does so depends upon the
natures and states of the channels. Fewer than the requested number of
bytes are transferred if this channel's file contains fewer than
{@code count} bytes starting at the given {@code position}, or if the
target channel is non-blocking and it has fewer than {@code count}
bytes free in its output buffer.
3.这么多判断,鬼知道到哪个if里面去,
[外链图片转存失败,源站可能有防盗链机制,建议将图片保存下来直接上传(img-LTohBvAH-1575611299778)(http://www.lmy25.wang:10700/upload/2019/12/image-fd6ed25d4f41411a8557786777460d0d.png)]
[外链图片转存失败,源站可能有防盗链机制,建议将图片保存下来直接上传(img-QcW1CnBL-1575611299779)(http://www.lmy25.wang:10700/upload/2019/12/image-4d5f2d1cc5f0473a8f6a9d07a3351ad2.png)]
主要是做一些基础判断,通道是否打开,是否可写,是不是fileChannel实例,position校验,以及确定当前可操作的字节数
//count是我们需要的字节数,而Integer.MAX_VALUE是最大字节数,2g,如果超过
//需要while像之前那个循环操作,但是我们为啥之前限制是8m呢?
int icount = (int)Math.min(count, Integer.MAX_VALUE);
// Attempt a direct transfer, if the kernel supports it
//如果内核支持,将直接进行传输,即zero-copy
if ((n = transferToDirectly(position, icount, target)) >= 0)
return n;
我们debug是进行到了第二个id,因此第一个if是不满足的,接着进入第一个if方法,看看为什么不满足,首先记住transferToDirectly(position, icount, target)返回值(-6)
[外链图片转存失败,源站可能有防盗链机制,建议将图片保存下来直接上传(img-1UxbLXpp-1575611299780)(http://www.lmy25.wang:10700/upload/2019/12/image-a4b121b0a9f54c839bf85728079c6a7e.png)]
2. 进入 transferToDirectly(position, icount, target)方法,也在FileChannelImpl,可以看见方法返回了很多常量
[外链图片转存失败,源站可能有防盗链机制,建议将图片保存下来直接上传(img-GQQTVrzu-1575611299781)(http://www.lmy25.wang:10700/upload/2019/12/image-76415ceec73a40dc9979aeb1753a030c.png)]
点过去看看这些常量
[外链图片转存失败,源站可能有防盗链机制,建议将图片保存下来直接上传(img-aGz8l92G-1575611299781)(http://www.lmy25.wang:10700/upload/2019/12/image-3cb91c2d00184ea4910b16cabfad050f.png)]```java
我们返回的-6正是 UNSUPPORTED_CASE
3. 回到方法,这几个这是返回的地方,继续debug验证
[外链图片转存失败,源站可能有防盗链机制,建议将图片保存下来直接上传(img-p8jn9aXU-1575611299782)(http://www.lmy25.wang:10700/upload/2019/12/image-588a6e7c085f4b4c82166c539770690a.png)]
发现在如下地方开始返回了
[外链图片转存失败,源站可能有防盗链机制,建议将图片保存下来直接上传(img-Y0rTwYIG-1575611299782)(http://www.lmy25.wang:10700/upload/2019/12/image-d76e37b8403c470b811da74fa3db1ad3.png)]
if (!nd.canTransferToDirectly(sc))
return IOStatus.UNSUPPORTED_CASE;
而nd定义是,使用本地方法读写,显然这里是没有使用本地方法读写的
// Used to make native read and write calls
private final FileDispatcher nd;
socketChannel.configureBlocking(true);
FileChannel fileChannel = new FileInputStream(p).getChannel();
进入getchannel()方法
[外链图片转存失败,源站可能有防盗链机制,建议将图片保存下来直接上传(img-yKiZIvUl-1575611299783)(http://www.lmy25.wang:10700/upload/2019/12/image-a3f1090014d5478ba7283a9da9f460de.png)]
this.channel = fc = FileChannelImpl.open(fd, path, true,
false, false, this);
记住direct变量传入的为false,进入open()方法
[外链图片转存失败,源站可能有防盗链机制,建议将图片保存下来直接上传(img-J5lvwI8q-1575611299783)(http://www.lmy25.wang:10700/upload/2019/12/image-632acb3daf014c8db2f7a100d3d8f824.png)]
接着进入FileChannelImpl构造器
[外链图片转存失败,源站可能有防盗链机制,建议将图片保存下来直接上传(img-UYOeSUEe-1575611299784)(http://www.lmy25.wang:10700/upload/2019/12/image-2577758d320c421c89fca329297a2e56.png)]
发现nd是直接new出来的,并且当前direct为false
this.nd = new FileDispatcherImpl();
SelectableChannel sc = (SelectableChannel)target;
if (!nd.canTransferToDirectly(sc))
return IOStatus.UNSUPPORTED_CASE;
进入canTransferToDirectly方法实现
[外链图片转存失败,源站可能有防盗链机制,建议将图片保存下来直接上传(img-5Qjqv2eP-1575611299784)(http://www.lmy25.wang:10700/upload/2019/12/image-eb3343835ac146be9ad3dff045726e3a.png)]
sc即为target强转的对象,由于之前我们配置了sockerChannel为bloking的(4中),所以当前 sc.isBlocking()为true,那么只有fastFileTransfer为false了
6. 查看fastFileTransfer定义
[外链图片转存失败,源站可能有防盗链机制,建议将图片保存下来直接上传(img-zsxqVLPa-1575611299784)(http://www.lmy25.wang:10700/upload/2019/12/image-f472e08076994afd95cea19b308ce152.png)]
还记得吗,在4中我们寻找nd的时候,最后nd来源
this.nd = new FileDispatcherImpl();
即直接new了一个,而当前fastFileTransfer也是定义在FileDispatcherImpl中的,即这时候fastFileTransfer并没有赋值,因此即为默认值false
7. 回到FileChannel判断是否能使用zero-copy的反方transferToDirectly
[外链图片转存失败,源站可能有防盗链机制,建议将图片保存下来直接上传(img-CSBL2FWw-1575611299785)(http://www.lmy25.wang:10700/upload/2019/12/image-f0702a7dc7584080bfe2a5f205e9048a.png)]
这时候就会返回UNSUPPORTED_CASE,即-6
8.之后回到我们transforTo的方法的第二个if,也就是产生返回值的if
[外链图片转存失败,源站可能有防盗链机制,建议将图片保存下来直接上传(img-UGo9blzs-1575611299785)(http://www.lmy25.wang:10700/upload/2019/12/image-9e1cf957233f45ae8aa015e13b3087da.png)]
// Attempt a mapped transfer, but only to trusted channel types
// 大致意思是尝试使用内存映射,不懂的可以了解下nio的内存映射
if ((n = transferToTrustedChannel(position, icount, target)) >= 0)
return n;
由于在此地返回,那么n肯定是大于0的,进入transferToTrustedChannel()方法查看
9. 进行基本判断之后,就开始传输,注意while循环里面的方法
[外链图片转存失败,源站可能有防盗链机制,建议将图片保存下来直接上传(img-0WJM6Tcv-1575611299786)(http://www.lmy25.wang:10700/upload/2019/12/image-56ebf4d07bdd45a99a00d0d92f3b169e.png)]
long remaining = count;
while (remaining > 0L) {
long size = Math.min(remaining, MAPPED_TRANSFER_SIZE);
try {
MappedByteBuffer dbb = map(MapMode.READ_ONLY, position, size);
当进入while循环,会在remaining和MAPPED_TRANSFER_SIZE常量之间取最小值,remaining即是我们的count,好像是318m吧,接着查看常量MAPPED_TRANSFER_SIZE定义,
[外链图片转存失败,源站可能有防盗链机制,建议将图片保存下来直接上传(img-99d1o06z-1575611299787)(http://www.lmy25.wang:10700/upload/2019/12/image-266c317b161a485c9dad145bae766922.png)]
现在明白了吧,当不满足zero-copy的时候会尝试使用内存映射,而内存映射的限制MAPPED_TRANSFER_SIZE是8m,因此我们使用transferTo方法向socketChannel拷贝数据的时候会限制在8m,而向其他的一些通道Channel拷贝的时候是限制在2g,原因就是底层使用的机制不一致导致的。
10. 后面map就是具体的内存映射实现代码了,可以看见返回值dbb,即是8m
[外链图片转存失败,源站可能有防盗链机制,建议将图片保存下来直接上传(img-khZVCT6x-1575611299787)(http://www.lmy25.wang:10700/upload/2019/12/image-bc6084f7a1524031b81b73fb6394b568.png)]