目录
- 一、FileUtils.copyFile
- 1、从实例出发
- 2、还是蛮快的,探索源码一番...
- 二、FileChannel
- 1、读操作
- 2、写操作
- 3、代码实例
- 4、控制台输出
- 三、如何减少copy和上下文切换的次数?
- 1、为什么不能舍弃内核空间这一步,直接读取到用户空间呢?
- 2、如何减少copy和上下文切换的次数?
- 3、代码实例
- 4、控制台输出
- 哪吒精品系列文章
一、FileUtils.copyFile
1、从实例出发
一般开发的时候,都是通过文件工具类进行文件的copy,那么它的性能怎么样呢?它是怎么实现的呢?今天就来分析以下FileUtils.copyFile。
private static void copyFileByUtils() {String srcFilePath = "H:\\CSDN\\JWFS.rar";// 文件大小 68.8 MBString destFilePath = "H:\\CSDN\\netty\\nio\\JWFS.rar";long start = System.currentTimeMillis();try {FileUtils.copyFile(new File(srcFilePath),new File(destFilePath));} catch (IOException e) {e.printStackTrace();}long end = System.currentTimeMillis();System.out.println("copyFileByUtils 消耗:"+(end-start)+"毫秒");}
2、还是蛮快的,探索源码一番…
private static void doCopyFile(File srcFile, File destFile, boolean preserveFileDate) throws IOException {if (destFile.exists() && destFile.isDirectory()) {throw new IOException("Destination '" + destFile + "' exists but is a directory");} else {FileInputStream fis = null;FileOutputStream fos = null;FileChannel input = null;FileChannel output = null;try {fis = new FileInputStream(srcFile);fos = new FileOutputStream(destFile);input = fis.getChannel();output = fos.getChannel();long size = input.size();long pos = 0L;for(long count = 0L; pos < size; pos += output.transferFrom(input, pos, count)) {count = size - pos > 31457280L ? 31457280L : size - pos;}} finally {IOUtils.closeQuietly(output);IOUtils.closeQuietly(fos);IOUtils.closeQuietly(input);IOUtils.closeQuietly(fis);}if (srcFile.length() != destFile.length()) {throw new IOException("Failed to copy full contents from '" + srcFile + "' to '" + destFile + "'");} else {if (preserveFileDate) {destFile.setLastModified(srcFile.lastModified());}}}}
发现了一个生僻的词汇,FileChannel,研究一下。
二、FileChannel
这种方式是“在非直接缓冲区中,通过Channel实现文件的复制”
。
1、读操作
- 将磁盘文件读取到操作系统OS提供的内核地址空间的内存中,第一次复制,OS上下文切换到内核模式;
- 将内核地址空间内存中的文件内容复制到JVM提供的用户地址空间的内存中,第二次复制,OS上下文切换到用户模式;
2、写操作
- 将用户地址空间的JVM内存中的文件内容复制到OS提供的内核地址空间中的内存中,第一次复制,OS上下文切换到内核模式;
- 将内核地址空间中内存的文件内容写入磁盘文件,第二次复制,写入操作完毕后,OS上下文最终切换到用户模式;
JVM控制的内存称为堆内内存,一般用Java操作的内存都属于堆内内存,堆内内存由JVM统一管理,根据上面的流程图可以发现,一次文件的读写要经过4次copy和4次用户控件与内核空间的上下文切换。
3、代码实例
package com.guor.demo.io;import java.io.File;
import java.io.FileInputStream;
import java.io.FileOutputStream;
import java.io.IOException;
import java.nio.ByteBuffer;
import java.nio.channels.FileChannel;public class CopyFileTest {private static void copyFileByChannel() {FileInputStream fileInputStream = null;FileOutputStream fileOutputStream = null;FileChannel intChannel = null;FileChannel outChannel = null;String srcFilePath = "H:\\CSDN\\JWFS.rar";// 文件大小 68.8 MBString destFilePath = "H:\\CSDN\\netty\\nio\\JWFS.rar";long start = System.currentTimeMillis();try {fileInputStream = new FileInputStream(srcFilePath);fileOutputStream = new FileOutputStream(destFilePath);// 获取通道intChannel = fileInputStream.getChannel();outChannel = fileOutputStream.getChannel();// 创建非直接缓冲区ByteBuffer buffer = ByteBuffer.allocate(1024);while (intChannel.read(buffer)!=-1){buffer.flip();outChannel.write(buffer);buffer.clear();}long end = System.currentTimeMillis();System.out.println("copyFileByChannel 消耗:"+(end-start)+"毫秒");}catch (Exception e){System.out.println(e);}finally {if(outChannel!=null){try {outChannel.close();} catch (IOException e) {}}if(intChannel!=null){try {intChannel.close();} catch (IOException e) {}}if(fileOutputStream!=null){try {fileOutputStream.close();} catch (IOException e) {}}if(fileInputStream!=null){try {fileInputStream.close();} catch (IOException e) {}}}}public static void main(String[] args) {copyFileByChannel();}
}
4、控制台输出
感觉和FileUtils.copyFile的速度还是有差距的。
三、如何减少copy和上下文切换的次数?
1、为什么不能舍弃内核空间这一步,直接读取到用户空间呢?
因为JVM中有GC,GC会不定期的清理没用的对象,并且压缩文件区域,如果某一时刻正在JVM中复制一个文件,但由于GC的压缩操作可能会引起文件在JVM中的位置发生改变,进而导致程序出现异常。因此,为了保证文件在内存中的位置不发生改变,只能将其放入OS的内存中。
2、如何减少copy和上下文切换的次数?
使用直接缓冲区,就可以在JVM中通过一个address变量指向OS中的一块内存(称为物理映射文件),之后,就可以通过JVM直接使用OS中的内存。
下面介绍一个新的方式“在直接缓冲区中,通过Channel实现文件的复制”
。
这样,数据的赋值操作都是在内核空间里进行的,用户空间与内核空间直接的复制次数为0,也就是零拷贝。
3、代码实例
private static void copyFileByMapped() {String srcFilePath = "H:\\CSDN\\JWFS.rar";// 文件大小 68.8 MBString destFilePath = "H:\\CSDN\\netty\\nio\\JWFS.rar";long start = System.currentTimeMillis();FileChannel inChannel = null;FileChannel outChannel = null;try {// 文件的输入通道inChannel = FileChannel.open(Paths.get(srcFilePath), StandardOpenOption.READ);// 文件的输出通道outChannel = FileChannel.open(Paths.get(destFilePath), StandardOpenOption.WRITE, StandardOpenOption.READ, StandardOpenOption.CREATE);// 输入通道和输出通道之间的内存映射文件,内存映射文件处于堆外内存MappedByteBuffer inMappedBuffer = inChannel.map(FileChannel.MapMode.READ_ONLY, 0, inChannel.size());MappedByteBuffer outMappedBuffer = outChannel.map(FileChannel.MapMode.READ_WRITE, 0, inChannel.size());// 直接对内存映射文件进行读写byte[] bytes = new byte[inMappedBuffer.limit()];inMappedBuffer.get(bytes);outMappedBuffer.put(bytes);long end = System.currentTimeMillis();System.out.println("copyFileByMapped 消耗:"+(end-start)+"毫秒");}catch (Exception e){System.out.println(e);}finally {try {inChannel.close();outChannel.close();} catch (IOException e) {}}}
4、控制台输出
直接缓冲区拷贝文件比非直接缓冲区拷贝文件,快了整整10倍。
哪吒精品系列文章
Java学习路线总结,搬砖工逆袭Java架构师
10万字208道Java经典面试题总结(附答案)
SQL性能优化的21个小技巧
Java基础教程系列
Spring Boot 进阶实战