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中)

发表评论

邮箱地址不会被公开。 必填项已用*标注