Java有哪些拷贝方式?
常见的同步阻塞就是利用FileInputStream和FileOutputStream进行拷贝
推荐的则是nio类库中transferTo和transferFrom方法
public static void copyFileByChannel(File source, File dest) throws
IOException { try (FileChannel sourceChannel = new FileInputStream(source) .getChannel(); FileChannel targetChannel = new FileOutputStream(dest).getChannel ();){ for (long count = sourceChannel.size() ;count>0 😉 { long transferred = sourceChannel.transferTo( sourceChannel.position(), count, targetChannel); sourceChannel.position(sourceChannel.position() + transferred); count -= transferred; } } } |
而且现在版本的Java标准类库中,也提供了Files.copy的实现
那么之间的不同之处在于什么地方呢?
在拷贝中
是需要涉及用户态和内核态的转换的
在使用输入输出流进行读取的时候,就牵扯到了上下文的切换,应用读取数据的时候,就是讲数据从硬盘读取到内核,在切换到了用户态从数据从内核缓存读取到应用缓存
上下文的切换必然会导致IO效率的降低
而基于NIO TransferTo的实现,在Linux和Unix上,使用了零拷贝,就是直接在内核态进行拷贝数据,省去了切换的开销
那么在Java,拷贝方式利用了哪种实现呢?
在copy方法上,有着如下几种
后两种是使用的transferTo,第一种的实现则可以如下的代码
public static Path copy(Path source, Path target, CopyOption… options)
throws IOException { FileSystemProvider provider = provider(source); if (provider(target) == provider) { // same provider provider.copy(source, target, options);//这是本文分析的路径 } else { // different providers CopyMoveHelper.copyToForeignTarget(source, target, options); } return target; } |
可以看到FileSystemProvider只是一个抽象类,里面使用了本地方法
最后可以看到是UnixCopyFile.c 利用了简单的用户态拷贝
那么从上面可以看出,对于copy文件,可以总结出
1.首先使用缓存等机制,减少IO次数
2.使用transferTo等机制,避免切换
3.减少不必要的转换,编解码,使用原本信息传输
然后关于我们在NIO操作数据的基本过程中,Java还为每一个原始数据类型提供了对应的Buffer实现,常见的有
Buffer是具有通用属性的
capacity,反应了总长度
position 这次数据操作的起始位置
limit 本次操作的限额
mark 上次postion的位置,默认为0
对于Buffer的使用
则是常见了一个ByteBuffer,放入数据
然后写入其他的数据的时候,posotion会跟着水涨船高
然后进行调出数据,使用flip,将position重置为0,limit设置为以前的position里面
如果想要重读,可以使用rewind函数
对于Buffer中,还有着利用了堆外内存的Buffer
Direct Buffer,就是定义了isDirect()方法,创建的是堆外的Buffer
MappedByteBuffer:将文件按照指定大小进行映射为了内存区域,程序直接利用这个内存地址来操作文件数据,避免上下文切换的损失
但是别忘了,因为直接操作的堆外内存,所以会比堆内Buffer增加开销,所以适用于长期使用,数据量大的场景
因为不在堆上,所以Xmx无法限制,也不能影响Direct Buffer等堆外成员的内存额度
但可以利用如下的参数设置
-XX:MaxDirecctMemorySize=512M
对于其的收集,往往是在full GC的时候,才会进行收集,所以容易导致OOM
所以对于Direct Buffer的使用和回收,最好显示的调用System.gc()来进行收集
在框架项目中,自己去释放
重复使用Direct Buffer
然后就是JVM关联的堆外内存的诊断和使用
在JDK 8之后的版本,可以利用Native Memory Tracking 来进行诊断
-XX:NativeMemoryTracking={summary|detail}
加上如上启动项就可以了
加上后可以利用如下的命令进行交互
具体的堆外内存中,是在Internal中,(JDK 11移到了other中)