Java基础知识 18通过FileUtils.copyFile探索IO原理
Posted 哪 吒
tags:
篇首语:本文由小常识网(cha138.com)小编为大家整理,主要介绍了Java基础知识 18通过FileUtils.copyFile探索IO原理相关的知识,希望对你有一定的参考价值。
目录
一、FileUtils.copyFile
1、从实例出发
一般开发的时候,都是通过文件工具类进行文件的copy,那么它的性能怎么样呢?它是怎么实现的呢?今天就来分析以下FileUtils.copyFile。
private static void copyFileByUtils()
String srcFilePath = "H:\\\\CSDN\\\\JWFS.rar";// 文件大小 68.8 MB
String 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 MB
String 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 MB
String 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基础知识 18通过FileUtils.copyFile探索IO原理的主要内容,如果未能解决你的问题,请参考以下文章