因为我这个人有备份文件的习惯,而且不止一个备份硬盘,所以如果使用我之前的方式,先格盘,再进行复制,费时费力不说,还容易误操作,毕竟是人手点,时间长了很容易一下没把文件全部划下来,所以我就用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数组读取