一个小工具,用来比较两个文件目录下的文件是否有增加、删除和修改。
逻辑并不复杂,先计算文件的MD5码,然后循环遍历所有文件,依次比较即可。
主要目的是想写一个多线程版本的,复习一下多线程方面的知识。
1.新建一个maven项目,引入maven依赖
<!--做文件校验用的依赖-->
<dependency>
<groupId>commons-codec</groupId>
<artifactId>commons-codec</artifactId>
<version>1.11</version>
</dependency>
<dependency>
<groupId>commons-io</groupId>
<artifactId>commons-io</artifactId>
<version>2.5</version>
</dependency>
<dependency>
<groupId>org.apache.commons</groupId>
<artifactId>commons-lang3</artifactId>
<version>3.5</version>
</dependency>
2.单线程版本的校验工具
- MD5生成
private static String getMD5(File file) {
MessageDigest MD5 = null;
try {
MD5 = MessageDigest.getInstance("MD5");
} catch (NoSuchAlgorithmException ne) {
ne.printStackTrace();
}
FileInputStream fileInputStream = null;
try {
fileInputStream = new FileInputStream(file);
byte[] buffer = new byte[8192];
int length;
while ((length = fileInputStream.read(buffer)) != -1) {
MD5.update(buffer, 0, length);
}
return new String(Hex.encodeHex(MD5.digest()));
} catch (IOException e) {
e.printStackTrace();
return null;
} finally {
try {
if (fileInputStream != null)
fileInputStream.close();
} catch (IOException e) {
e.printStackTrace();
}
}
}
- 文件校验方法
public void checkFile(String oldPath, String newPath,String outPath) throws IOException {
long startTime = System.currentTimeMillis(); //获取开始时间
//三个set,分别放置更改文件、新增文件、删除文件
Set<String> changedFilesSet = new HashSet<>();
Set<String> newFilesSet = new HashSet<>();
Set<String> deletedFilesSet = new HashSet<>();
// 遍历新文件目录
Iterator iterator = FileUtils.iterateFiles(new File(newPath), null, true);
// 遍历旧文件目录
Iterator iterator1 = FileUtils.iterateFiles(new File(oldPath), null, true);
try {
// 遍历比较新旧目录
// 1. 如果文件不存在,则说明是新增的文件,放到newFilesSet
// 2. 如果MD5值不一样,则文件被修改,放到changedFilesSet
while (iterator.hasNext()) {
// 新文件路径
String nPathStr = iterator.next().toString();
File newFile = new File(nPathStr);
// 旧文件路径
File oldFile = new File(nPathStr.replace(newPath, oldPath));
// 判断文件是否存在
if (!oldFile.exists()) {
newFilesSet.add(newFile.toString());
} else {
// MD5校验
// 如果不相同,则文件被修改,放到changedFilesSet
String newMD5 = getMD5(newFile);
String oldMD5 = getMD5(oldFile);
if (!StringUtils.equals( newMD5, oldMD5 )) {
changedFilesSet.add(newFile.toString());
}
}
}
// 遍历旧的文件目录,用于查找被删除的文件
while (iterator1.hasNext()) {
// 旧文件路径
String oPathStr = iterator1.next().toString();
// 新文件路径
File newFile = new File(oPathStr.replace(oldPath, newPath));
if (!newFile.exists()) {
deletedFilesSet.add(oPathStr);
}
}
} catch (Exception e) {
System.err.println("发生异常!");
}
//创建文件
SimpleDateFormat df = new SimpleDateFormat("yyyy-MM-dd-HH-mm-ss-SSS");
String fileName = outPath + "\\" + df.format(new Date()) + ".txt";
//写入txt文件
File file = new File(fileName);
boolean newFile = file.createNewFile();
try {
PrintStream ps = new PrintStream(new FileOutputStream(file));
ps.append("旧文件目录:").append(oldPath).append("\n");
ps.append("新文件目录:").append(newPath).append("\n");
ps.append("新增文件:\n");
Iterator<String> newFilesPath = newFilesSet.iterator();
while (newFilesPath.hasNext()) {
ps.append(" ").append(String.valueOf(newFilesPath.next())).append("\n");
}
ps.append("删除文件:\n");
Iterator<String> deletedDilesPath = deletedFilesSet.iterator();
while (deletedDilesPath.hasNext()) {
ps.append(" ").append(String.valueOf(deletedDilesPath.next())).append("\n");
}
ps.append("修改文件:\n");
Iterator<String> changedFilesPath = changedFilesSet.iterator();
while (changedFilesPath.hasNext()) {
ps.append(" ").append(String.valueOf(changedFilesPath.next())).append("\n");
}
} catch (FileNotFoundException e) {
e.printStackTrace();
}
long endTime=System.currentTimeMillis(); //获取结束时间
System.out.println("校核完成 耗时:" + (endTime - startTime) + "ms");
}
3.多线程版本
分别写三个个线程,一个线程做增加文件的校验,一个做修改文件的校验,一个文件做删除文件的校验
新增文件校验线程
public class newFilePathCheck implements Runnable{
private String oldPath;
private String newPath;
private Set<String> newFilesSet;
public newFilePathCheck(String oldPath, String newPath, Set<String> newFilesSet) {
this.oldPath = oldPath;
this.newPath = newPath;
this.newFilesSet = newFilesSet;
}
@Override
public void run() {
long startTime = System.currentTimeMillis(); //获取开始时间
// 遍历新文件目录
Iterator iterator = FileUtils.iterateFiles(new File(newPath), null, true);
try {
// 遍历比较新旧目录
// 1. 如果文件不存在,则说明是新增的文件,放到newFilesSet
// 2. 如果MD5值不一样,则文件被修改,放到changedFilesSet
while (iterator.hasNext()) {
// 新文件路径
String nPathStr = iterator.next().toString();
File newFile = new File(nPathStr);
// 旧文件路径
File oldFile = new File(nPathStr.replace(newPath, oldPath));
// 判断文件是否存在
if (!oldFile.exists()) {
newFilesSet.add(newFile.toString());
}
}
}catch (Exception e) {
System.err.println("发生异常!");
}
long endTime=System.currentTimeMillis(); //获取结束时间
System.out.println("新增文件分析完成 耗时:" + (endTime - startTime) + "ms");
}
}
修改文件校验线程
public class changedFilePathCheck implements Runnable{
private String oldPath;
private String newPath;
private Set<String> changedFilesSet;
public changedFilePathCheck(String oldPath, String newPath, Set<String> changedFilesSet) {
this.oldPath = oldPath;
this.newPath = newPath;
this.changedFilesSet = changedFilesSet;
}
@Override
public void run() {
long startTime = System.currentTimeMillis(); //获取开始时间
// 遍历新文件目录
Iterator iterator = FileUtils.iterateFiles(new File(newPath), null, true);
try {
// 遍历比较新旧目录
// 1. 如果文件不存在,则说明是新增的文件,放到newFilesSet
// 2. 如果MD5值不一样,则文件被修改,放到changedFilesSet
while (iterator.hasNext()) {
// 新文件路径
String nPathStr = iterator.next().toString();
File newFile = new File(nPathStr);
// 旧文件路径
File oldFile = new File(nPathStr.replace(newPath, oldPath));
// 判断文件是否存在
if (oldFile.exists()) {
// MD5校验
// 如果不相同,则文件被修改,放到changedFilesSet
String newMD5 = getMD5(newFile);
String oldMD5 = getMD5(oldFile);
if (!StringUtils.equals( newMD5, oldMD5 )) {
changedFilesSet.add(newFile.toString());
}
}
}
}catch (Exception e) {
System.err.println("发生异常!");
}
long endTime=System.currentTimeMillis(); //获取结束时间
System.out.println("修改文件分析完成 耗时:" + (endTime - startTime) + "ms");
}
}
删除文件校验教程
public class oldFilePathCheck implements Runnable{
private String oldPath;
private String newPath;
private Set<String> deletedFilesSet;
public oldFilePathCheck(String oldPath, String newPath, Set<String> deletedFilesSet) {
this.oldPath = oldPath;
this.newPath = newPath;
this.deletedFilesSet = deletedFilesSet;
}
@Override
public void run() {
long startTime = System.currentTimeMillis(); //获取开始时间
// 遍历旧文件目录
Iterator iterator1 = FileUtils.iterateFiles(new File(oldPath), null, true);
try {
// 遍历旧的文件目录,用于查找被删除的文件
while (iterator1.hasNext()) {
// 旧文件路径
String oPathStr = iterator1.next().toString();
// 新文件路径
File newFile = new File(oPathStr.replace(oldPath, newPath));
if (!newFile.exists()) {
deletedFilesSet.add(oPathStr);
}
}
} catch (Exception e) {
System.err.println("发生异常!");
}
long endTime=System.currentTimeMillis(); //获取结束时间
System.out.println("删除文件分析完成 耗时:" + (endTime - startTime) + "ms");
}
}
最后是主线程
public void checkFile(String oldPath, String newPath,String outPath) throws InterruptedException, IOException {
long startTime = System.currentTimeMillis(); //获取开始时间
//三个set,分别放置更改文件、新增文件、删除文件
Set<String> changedFilesSet = new HashSet<>();
Set<String> newFilesSet = new HashSet<>();
Set<String> deletedFilesSet = new HashSet<>();
Thread newFilePathCheck = new Thread(new newFilePathCheck(oldPath, newPath, newFilesSet));
Thread changedFilePathCheck = new Thread(new changedFilePathCheck(oldPath, newPath, changedFilesSet));
Thread oldFilePathCheck = new Thread(new oldFilePathCheck(oldPath, newPath, deletedFilesSet));
newFilePathCheck.start();
changedFilePathCheck.start();
oldFilePathCheck.start();
//等三个线程都执行完毕再执行主线程
newFilePathCheck.join();
changedFilePathCheck.join();
oldFilePathCheck.join();
//创建文件
SimpleDateFormat df = new SimpleDateFormat("yyyy-MM-dd-HH-mm-ss-SSS");
String fileName = outPath + "\\" + df.format(new Date()) + ".txt";
//写入txt文件
File file = new File(fileName);
boolean newFile = file.createNewFile();
try {
PrintStream ps = new PrintStream(new FileOutputStream(file));
ps.append("旧文件目录:").append(oldPath).append("\n");
ps.append("新文件目录:").append(newPath).append("\n");
ps.append("新增文件:\n");
Iterator<String> newFilesPath = newFilesSet.iterator();
while (newFilesPath.hasNext()) {
ps.append(" ").append(String.valueOf(newFilesPath.next())).append("\n");
}
ps.append("删除文件:\n");
Iterator<String> deletedDilesPath = deletedFilesSet.iterator();
while (deletedDilesPath.hasNext()) {
ps.append(" ").append(String.valueOf(deletedDilesPath.next())).append("\n");
}
ps.append("修改文件:\n");
Iterator<String> changedFilesPath = changedFilesSet.iterator();
while (changedFilesPath.hasNext()) {
ps.append(" ").append(String.valueOf(changedFilesPath.next())).append("\n");
}
} catch (FileNotFoundException e) {
e.printStackTrace();
}
long endTime=System.currentTimeMillis(); //获取结束时间
System.out.println("多线程校核完成 耗时:" + (endTime - startTime) + "ms");
}
4.对比一下两种方法的时间耗费
public static void main( String[] args ) throws IOException, InterruptedException {
String oldPath = "D:\\test\\oldPath";
String newPath = "D:\\test\\newPath";
String outPath = "D:\\test\\outPath";
FileCheckSingleThread fileCheckSingleThread = new FileCheckSingleThread();
fileCheckSingleThread.checkFile(oldPath, newPath, outPath);
FileCheckMultiThread fileCheckMultiThread = new FileCheckMultiThread();
fileCheckMultiThread.checkFile(oldPath, newPath, outPath);
}
单线程校核完成 耗时:1569ms
新增文件分析完成 耗时:414ms
删除文件分析完成 耗时:434ms
修改文件分析完成 耗时:1031ms
多线程校核完成 耗时:1042ms
可以看到由于并行执行,虽然所有线程的执行时间加起来是比原来的要多,但是总体时间减少了,算是充分利用了cpu资源。