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、读操作

  1. 将磁盘文件读取到操作系统OS提供的内核地址空间的内存中,第一次复制,OS上下文切换到内核模式;
  2. 将内核地址空间内存中的文件内容复制到JVM提供的用户地址空间的内存中,第二次复制,OS上下文切换到用户模式;

2、写操作

  1. 将用户地址空间的JVM内存中的文件内容复制到OS提供的内核地址空间中的内存中,第一次复制,OS上下文切换到内核模式;
  2. 将内核地址空间中内存的文件内容写入磁盘文件,第二次复制,写入操作完毕后,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学习路线总结,搬砖工逆袭Java架构师

10万字208道Java经典面试题总结(附答案)

SQL性能优化的21个小技巧

Java基础教程系列

Spring Boot 进阶实战

以上是关于Java基础知识 18通过FileUtils.copyFile探索IO原理的主要内容,如果未能解决你的问题,请参考以下文章

JAVA的18条BASE

# Day18-Java基础

JAVA 基础编程练习题18 程序 18 乒乓球赛

JAVA基础知识总结18(反射)

java知识总结-18

Java基础知识回顾-18(Math类,Arrays类和大数据运算)