番外篇---一次文件拷贝对Java io流的思考

因为我这个人有备份文件的习惯,而且不止一个备份硬盘,所以如果使用我之前的方式,先格盘,再进行复制,费时费力不说,还容易误操作,毕竟是人手点,时间长了很容易一下没把文件全部划下来,所以我就用Java自己写了一个文件备份工具类,如下

import java.io.File;
import java.io.FileInputStream;
import java.io.FileOutputStream;
import java.io.IOException;

/**
 * 2019-11-12 15:48
 * 贺驰宇
 */
public class CheckFileDate {
    //需要备份的文件夹位置
    private String copy;
    //备份存放的位置
    private String paste;
    //缓存数组
    private byte[] bytes = new byte[1024 * 1024];

    public CheckFileDate(String copy,String paste){
        this.copy=copy;
        this.paste=paste;
    }

    /**
     * 根据修改时间对文件进行更新操作
     */
    private void copyFile(String copy, String paste) {
        FileInputStream copyFis = null;
        FileOutputStream pasteFos = null;

        File copyFile = new File(copy);
        File pasteFile = new File(paste);
        long copyDate;
        //复制的文件是否存在
        if (copyFile.exists()) {
            //获取复制文件的最后修改日期
            copyDate = copyFile.lastModified();
            //被修改的文件是否存在
            if (pasteFile.exists()) {
                //若被修改的文件修改日期晚于复制的文件
                if (pasteFile.lastModified() >= copyDate)
                    return;
                    //否则删除被修改的文件
                else
                    pasteFile.delete();
            } else {
                //若被修改的文件不存在,查看被修改的文件上级文件夹是否存在
                File fileParent = pasteFile.getParentFile();
                if (fileParent != null && !fileParent.exists())
                    fileParent.mkdirs();
            }
            try {
                copyFis = new FileInputStream(copyFile);
                pasteFos = new FileOutputStream(pasteFile);
                int bytesIndex = -1;
                while ((bytesIndex = copyFis.read(bytes)) != -1) {
                    pasteFos.write(bytes, 0, bytesIndex);
                }
                //修改文件最后修改时间
                pasteFile.setLastModified(copyDate);
            } catch (Exception e) {
                e.printStackTrace();
            } finally {
                try {
                    if (copyFis != null)
                        copyFis.close();
                    if (pasteFos != null) {
                        pasteFos.flush();
                        pasteFos.close();
                    }
                } catch (IOException e) {
                    e.printStackTrace();
                }
            }
        }
    }

    public static void main(String[] args) throws Exception {
        new CheckFileDate("D:\\","F:\\").start();
    }
    public void start(){
        listAllFiles(new File(copy));
    }
    /**
     * 递归遍历所有文件,并尝试进行更新
     */
    private void listAllFiles(File file) {
        //是文件,尝试进行更新
        if (file.isFile()) {
            copyFile(file.getAbsolutePath(), file.getAbsolutePath().replace(copy, paste));
        } else {
            //若为文件夹,进行递归
            if (file.exists() && file.isDirectory()) {
                //打印当前备份的文件夹
                System.out.println(file.getAbsolutePath());
                File[] files = file.listFiles();
                //防止空文件夹导致报错
                if (files != null)
                    for (File f : files)
                        listAllFiles(f);
            }
        }
    }
}

我稍后可能会对这个工具类进行修改,增加一个完全同步的选项,删除备份盘的多余文件和文件夹,不过这都是后话了,主要的问题是这个,按照常理来说,byte缓存数组越大,应当备份的速度越快,问题来了,经过的我测试,并不是这样,反而当byte数组到达某个值的时候,速度越来越慢,我开始以为是因为数组太大,导致频繁gc所致,但当我尝试添加-Xloggc:C:\Users\hh\Desktop\gcinfo.log参数打印cg日志,发现并没有gc,我百思不得其解,突然灵光乍现,既然不是cg的问题,那么有没有可能是io流本身的问题呢?我们知道使用数组进行读取比一个字节一个字节读取要快得多,反过来说,是不是数组太长了,一样会影响效率呢?虽然由于read对应的最终实现方法是native修饰,我无法进一步追踪下去,但是我们可以修改数组大小查看这个问题,先尝试一下大文件

public class test {
    public static void main(String[] args) {
        long l=System.currentTimeMillis();
        new CheckFileDate("E:\\test","E:\\test1").start();
        System.out.println(System.currentTimeMillis()-l);
    }
}

在这里插入图片描述
在这里插入图片描述
3个系统安装包,一共10G多一点,耗时13秒,把数组大小调大到1G,继续测试,速度几乎慢了一倍
在这里插入图片描述
测试小文件
在这里插入图片描述
直接把maven仓库放进去,大概5000多个文件,测试正常大小的数组
在这里插入图片描述
5秒,测试1g的byte数组,大概10分钟传输了1000多个文件,实在懒得等了,就结束了测试
在这里插入图片描述
可以很明显的看出来,随着byte数组的变大,效率急速降低,我又做了一个实验,为CheckFileDate添加一个构造函数,直接修改byte数组的大小,观察变化,因为信息太长,我节选其中一部分,可以看出来,对于byte数组较小的情况下,byte确实对效率提升很有帮助

public class test {
    public static void main(String[] args) {
        for(int i=1;i<1024*1024*1024;i++) {
            long l = System.currentTimeMillis();
            new CheckFileDate("E:\\test", "E:\\test1",i).start();
            new File("E:\\test1\\spring-context-4.1.6.RELEASE.jar").delete();
            System.out.println("byte数组为"+i+"所需时间"+(System.currentTimeMillis() - l));
        }
    }
}

在这里插入图片描述
那如果以KB为单位呢?让我们看看会发生什么,显然,几乎没有变化,那么MB呢?
在这里插入图片描述
随着数组慢慢变大,明显变慢
在这里插入图片描述
我认真思考了这个问题,我们知道,io的本质其实就是从一个储存介质获取数据,然后有可能还要把这些数据放到另外一个储存介质中,所以io的本质其实就是储存介质之间对接的一个东西,那么储存介质是怎么读取数据的呢?其实,硬盘被分成一个个扇区,我们对硬盘的读取本质上就是对硬盘扇区的读取,我们知道4K对齐可以提升硬盘效率,也就是说,对于硬盘来说,每次读取其实就是对扇区进行读取,如果我们使用远远大于扇区的byte数组,硬盘就需要读取相当于byte数组大小/扇区大小次的数据,如果是一个大文件还好,损失的性能不多,但是如果是一个小文件,就直接炸了,我们进行最后一个实验,如果我们用大得多的byte数组尝试读取,损失的数组中是否存在数据

import java.io.File;
import java.io.FileInputStream;
import java.io.IOException;

public class test {
    public static void main(String[] args) {
        File copyFile = new File("E:\\test\\spring-context-4.1.6.RELEASE.jar");
        byte[] bytes = new byte[1024 * 1024 * 10086];
        FileInputStream copyFis = null;
        try {
            copyFis = new FileInputStream(copyFile);
            copyFis.read(bytes);
            System.out.println(bytes[1024 * 1024 * 199]);
        } catch (Exception e) {
            e.printStackTrace();
        } finally {
            try {
                if (copyFis != null)
                    copyFis.close();
            } catch (IOException e) {
                e.printStackTrace();
            }
        }
    }
}

在这里插入图片描述
结果是不存在数据,我查阅了微软的api,也就是说,至少微软和Java其中之一阻止了指针越界读取,而这显然是需要消耗性能的,这也可以解释为什么当数组极大的时候,CPU性能也会被大量消耗(此处我只是使用了1G的byte数组读取了一个1MB的jar包),具体细节等到我去阅读openjdk的时候再去细看,所以,请使用1024字节或1024*4(4K)字节的byte数组读取
在这里插入图片描述
在这里插入图片描述

  • 0
    点赞
  • 0
    收藏
    觉得还不错? 一键收藏
  • 2
    评论
评论 2
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值