一个diff工具,用于判断两个目录下所有的改动(比较新旧版本文件夹)
需求:
编写一个diff工具,用于判断两个目录下所有的改动
详细介绍:
- 有A和B两个目录,目录所在位置及层级均不确定
- 需要以B为基准找出两个目录中所有有改动的文件(文件或内容增加、修改、删除),将有改动的文件放入第三个目录中,层级结构与原目录相同
- 将所有新增与更新信息记录到更新日志文件中
- 将删除信息单独记录到删除日志文件中
- 每次执行diff工具需要生成一个新的以日期命名的目录存放文件
使用场景:
本工具用于软件版本升级时找出两个版本间所有修改过的文件,便于增量替换。
提示: 使用CRC判断文件是否改动
依赖的Jar包:

代码如下:
package test2; import java.io.File;
import java.io.IOException;
import java.text.SimpleDateFormat;
import java.util.ArrayList;
import java.util.Date;
import java.util.HashMap;
import java.util.List;
import java.util.Map;
import org.apache.commons.io.FileUtils;
import org.slf4j.Logger;
import org.slf4j.LoggerFactory; public class DiffUtil { private static Logger logger = LoggerFactory.getLogger(DiffUtil.class);// slf4j的日志记录器 /**
* 对比文件
* @param oldDir 旧版本文件(需求中的A文件夹)
* @param nowDir 新版本文件(需求中的B文件夹)
* @param diffDir 生成对比结果的文件夹(需求中的change文件夹)
*/
public static void compareFile(String oldDir, String nowDir, String diffDir) {
long startTime = System.currentTimeMillis();// 开始时间
// 1.在change文件夹下面生成一个当前日期格式的文件夹
String currentTime = convertCurrentTime2String();
String fileAndLogDir = diffDir + "\\" + currentTime;// 存放日志和更新后的文件的目录
File fileDiffDir = new File(fileAndLogDir);
fileDiffDir.mkdirs(); // 2.获取旧版本文件夹下和新版本文件夹下面的文件的CRC校验码
Map<String, Long> oldFileCRCs = getAllFileNameAndCRC(oldDir, oldDir,
new HashMap<String, Long>());
Map<String, Long> nowFileCRCs = getAllFileNameAndCRC(nowDir, nowDir,
new HashMap<String, Long>()); // 3.遍历删除的文件且将日志信息输出到deleteFile.log
String deleteLogName = "deleteFile.log";
File deleteLogFile = new File(fileDiffDir, deleteLogName);
// 3.1遍历旧文件夹下面的map的key,如果在新文件夹的map中找不到匹配的key值,证明是删除文件了
logger.info("----开始记录删除日志:" + convertCurrentTime2String() + "----");
try {
FileUtils.write(deleteLogFile, "-----开始记录删除日志:"
+ convertCurrentTime2String() + "----\r\n", "UTF-8", true);
} catch (IOException e) {
logger.error("将删除日志写入文件deteFile.log出错", e);
} List<String> deleteFileNames = new ArrayList<String>();
for (String oldKey : oldFileCRCs.keySet()) {
if (!nowFileCRCs.containsKey(oldKey)) {
logger.info("删除文件\t" + oldKey);
try {
FileUtils.write(deleteLogFile, "删除文件\t" + oldKey + "\r\n",
"UTF-8", true);
} catch (IOException e) {
logger.error("将删除日志写入文件deteFile.log出错", e);
}
deleteFileNames.add(oldKey);
}
} try {
FileUtils.write(deleteLogFile, "\r\n", "UTF-8", true);
FileUtils.write(deleteLogFile, "---------删除文件日志结束:共删除"
+ deleteFileNames.size() + "个文件----" + "\r\n", "UTF-8",
true);
} catch (IOException e) {
logger.error("将删除日志的统计信息写入文件deteFile.log出错", e);
}
logger.info("-----删除文件日志结束:共删除" + deleteFileNames.size() + "个文件----"); // 4.遍历增加和更新的文件
String addAndUpdateLogName = "addAndUpdate.log";
File addUpdateLogFile = new File(fileDiffDir, addAndUpdateLogName);
logger.info("-----开始记录增加、更新日志------");
List<String> addFileNames = new ArrayList<String>();// 增加文件名字集合
List<String> updateFileNames = new ArrayList<String>();// 更新文件名字集合
for (String nowKey : nowFileCRCs.keySet()) {
if (!oldFileCRCs.containsKey(nowKey)) {
addFileNames.add(nowKey);
} else {
if (oldFileCRCs.get(nowKey).equals(nowFileCRCs.get(nowKey))) {
continue;
}
updateFileNames.add(nowKey);
}
} // 4.1新增文件写入日志
try {
FileUtils.write(addUpdateLogFile, "-----Diff时间:"
+ convertCurrentTime2String() + "----" + "\r\n", "UTF-8",
true);
FileUtils.write(addUpdateLogFile, "\r\n", "UTF-8", true);
FileUtils.write(addUpdateLogFile, "----共新增文件" + addFileNames.size()
+ "个----\r\n", "UTF-8", true);
logger.info("----共新增文件" + addFileNames.size() + "个----");
} catch (IOException e1) {
logger.error("将新增信息写入文件addAndUpdate.log出错", e1);
} for (String addFileName : addFileNames) {
try {
logger.info("增加了文件" + addFileName);
FileUtils.write(addUpdateLogFile, "增加了文件" + addFileName
+ "\r\n", "UTF-8", true);
} catch (IOException e) {
logger.error("将新增信息写入文件addAndUpdate.log出错", e);
}
} // 4.2更新信息写入日志
try {
FileUtils.write(addUpdateLogFile, "\r\n", "UTF-8", true);
FileUtils.write(addUpdateLogFile,
"----共更新文件" + updateFileNames.size() + "个----\r\n",
"UTF-8", true);
logger.info("----共更新文件" + updateFileNames.size() + "个----");
} catch (IOException e) {
logger.error("将更新信息写入文件addAndUpdate.log出错", e);
}
for (String updateFileName : updateFileNames) {
try {
FileUtils.write(addUpdateLogFile, "更新了文件" + updateFileName
+ "\r\n", "UTF-8", true);
logger.info("更新了文件" + updateFileName);
} catch (IOException e) {
logger.error("将更新信息写入文件addAndUpdate.log出错", e);
}
}
// 5.将有新增/更新的文件放入第三个目录中(文件拷贝)
filesCopy(addFileNames, nowDir, diffDir + "\\"+ currentTime);
filesCopy(updateFileNames, nowDir, diffDir + "\\"+ currentTime);
long endTime = System.currentTimeMillis();// 结束时间
logger.info("----运行结束,耗时" + (endTime - startTime) + "ms----");
// 6.写入程序运行时间到日志文件
try {
FileUtils.write(addUpdateLogFile, "----运行结束,耗时"
+ (endTime - startTime) + "ms----" + "\r\n", "UTF-8", true);
FileUtils.write(deleteLogFile, "----运行结束,耗时"
+ (endTime - startTime) + "ms----" + "\r\n", "UTF-8", true);
} catch (IOException e) {
logger.error("将运行耗时写入日志文件出错", e);
}
} /**
* 将新增的文件和更新的文件复制到第三个文件夹(开源jar包实现文件拷贝)
* @param fileNames 文件名字集合
* @param nowDir 当前所在的目录
* @param diffDir 目的目录
*/
private static void filesCopy(List<String> fileNames,
String nowDir, String diffDir) {
File srcFile = null,destFile = null , destFileDir = null;
for (String sourceFileName : fileNames) {
srcFile = new File(nowDir+"\\"+sourceFileName);
destFile = new File(diffDir, sourceFileName);
String fileName = srcFile.getName();
destFileDir = new File((diffDir + "\\" + sourceFileName).replace(
fileName, ""));
destFileDir.mkdirs();
try {
FileUtils.copyFile(srcFile, destFile);
} catch (IOException e) {
logger.error("复制文件出错",e);
}
}
} /**
* 获取指定文件夹下面的所有文件,key是文件的名字(去掉基层路径),value是CRC冗余检验码(递归遍历)
* @param baseDir 基层路径
* @param fileDir 真实文件名字(去掉基层路径形成key)
* @param resultMap 结果(所有文件的CRC32码,key是真实文件名去掉基层路径,Value是CRC32码)
* @return 所有文件的CRC32码,key是真实文件名去掉基层路径,Value是CRC32码
*/
private static Map<String, Long> getAllFileNameAndCRC(String baseDir,
String fileDir, Map<String, Long> resultMap) {
File file = new File(fileDir);
if (!file.exists()) {// 文件不存在直接返回
return null;
} if (file.isDirectory()) {// 如果是目录,继续递归遍历获取其下面的所有文件的CRC32码
for (File f : file.listFiles()) {
getAllFileNameAndCRC(baseDir, f.getAbsolutePath(), resultMap);
}
} else {// 如果是文件,获取文件的CRC32码并添加到map中
long fileCRC = 0l;
try {
fileCRC = FileUtils.checksumCRC32(file);
} catch (IOException e) {
logger.error("获取文件的CRC32出错",e);
}
resultMap.put(file.getAbsolutePath().replace(baseDir, ""), fileCRC);
} return resultMap;
} /**
* 将当前日期转换为指定格式的字符串
* @return yyyy年MM月dd日HH时mm分ss秒 格式的日期串
*/
private static String convertCurrentTime2String() {
SimpleDateFormat sdf = new SimpleDateFormat("yyyy年MM月dd日HH时mm分ss秒");
return sdf.format(new Date());
} }
测试:
package test2;
public class MyTest {
public static void main(String[] args) {
// 1.第一种测试方式,直接将需要对比的文件夹写死在程序中运行
String oldDir = "C:\\Users\\Administrator\\Desktop\\mytest\\A";
String nowDir = "C:\\Users\\Administrator\\Desktop\\mytest\\B";
String diffDir = "C:\\Users\\Administrator\\Desktop\\mytest\\change";
//第二种方式,cmd窗口传参数进行运行
/*if (args == null || args.length != 3) {
System.out
.println("参数不全,使用方式java -jar DiffUtils.jar 原路径名 新路径名 diff目录路径");
return;
}
String oldDir = args[0];
String nowDir = args[1];
String diffDir = args[2];*/
DiffUtil.compareFile(oldDir, nowDir, diffDir);
}
}
我已经将此工具作为一个jar包打包起来,下载地址: http://qiaoliqiang.cn/fileDown/DiffUtil.jar
运行方式:
java -jar DiffUtil.jar C:\Users\liqiang\Desktop\新建文件件夹\考核1 C:\Users\liqiang\Desktop\新建文件夹\change
总结:
1. 文件复制有多种方式,可以用 FileUtils.copyFile(srcFile, destFile); 只需要传递两个File参数,第一个是源文件,第二个是目的文件。
也可以用 IOUtils.copy(inputStream, outputStream); 传递两个参数,第一个输入流,第二个是输出流。
2. 获取文件的CRC32循环冗余检验码也有多种方式,可以直接用 FileUtils.checksumCRC32(file); 直接获取
也可以用下面的工具方法获取:
/**
* 获取文件的CRC
*
* @param file
* 需要获取CRC32码的文件
* @return 文件的CRC32循环冗余码
*/
private static long getFileCRC(File file) {
BufferedInputStream bsrc = null;
CRC32 crc = new CRC32();
try {
bsrc = new BufferedInputStream(new FileInputStream(file));
byte[] bytes = new byte[1024];
int i;
while ((i = bsrc.read(bytes)) != -1) {
crc.update(bytes, 0, i);
}
} catch (Exception e) {
logger.error("计算文件的CRC32循环冗余检验出错", e);
} finally {
if (bsrc != null) {
try {
bsrc.close();
} catch (IOException e) {
logger.error("计算文件的CRC32循环冗余检验出错", e);
}
}
}
return crc.getValue();
}
3.日志记录也有多种方法,第一种使用log4j,获取logger的方法如下: Logger logger = Logger.getLogger(ApArrangeCourseAuditController.class);
第二种使用slf4j,获取logger的方法如下: private Logger logger = LoggerFactory.getLogger(ExtUserController.class);
一个diff工具,用于判断两个目录下所有的改动(比较新旧版本文件夹)的更多相关文章
- 代码实现:判断E盘目录下是否有后缀名为.jpg的文件,如果有,就输出该文件名称
package com.loaderman.test; import java.io.File; import java.io.FilenameFilter; public class Test { ...
- Shell 实现找出两个目录下的同名文件方法
# 首先我们来创建一些 2 个目录,里面的目录结构及相关文件如下所示: # 从上面的测试目录可以看到, lol.txt lol2.txt 两个文件是两个目录下的同名文件 # 有实际例子,思路就容易出来 ...
- [No00006B]方便的网络下载工具wget 可下载网站目录下的所有文件(可下载整个网站)
wget是linux下命令行的下载工具,功能很强大,它能完成某些下载软件所不能做的,比如如果你想下载一个网页目录下的所有文件,如何做呢?网络用户有时候会遇到需要下载一批文件的情况,有时甚至需要把整个网 ...
- linux中/etc与/var目录,各是什么意思?这两个目录下的文件有什么特点?
http://zhidao.baidu.com/link?url=DkxU9CyhJb_dIUAPCmPmxRtQsENgCzqy5qnLPEj_V9DqNzdt6Qya0U5iCVRCYFkgoRo ...
- 文件名命工具类(将指定目录下的文件的type类型的文件,进行重命名,命名后的文件将去掉type)
import java.io.File; /** * <b>function:</b> 文件命名工具类 * @author hoojo * @createDate 2012-5 ...
- 用Python删除本地目录下某一时间点之前创建的所有文件
因为工作原因,需要定期清理某个文件夹下面创建时间超过1年的所有文件,所以今天集中学习了一下Python对于本地文件及文件夹的操作.网上 这篇文章 简明扼要地整理出最常见的os方法,抄袭如下: os.l ...
- 2.每人自己建立一个HelloWorld项目,练习使用git的add/commit/push/pull/fetch/clone等基本命令。比较项目的新旧版本的差别。答题人:张立鹏
第1步:创建SSH Key.在用户主目录下,看看有没有.ssh目录,如果有,再看看这个目录下有没有id_rsa和id_rsa.pub这两个文件,如果已经有了,可直接跳到下一步.如果没有,打开Shell ...
- 第二章——建立一个HelloWorld项目,练习使用git的add/commit/push/pull/fetch/clone等基本命令。比较项目的新旧版本的差别-----答题者:徐潇瑞
1.首先下载安装git,很简单所以就不详细说了,当弹出一个类似的命令窗口的东西,就说明Git安装成功 2.因为Git是分布式版本控制系统,所以需要填写用户名和邮箱作为一个标识 3.接着,注册githu ...
- Pycharm学习记录---同一目录下无法import明明已经存在的.py文件
转自:https://blog.csdn.net/l8947943/article/details/79874180 问题描述: 如图:同目录下明明存在相应文件,在导入时却出现带有红色波浪线,说没有相 ...
随机推荐
- 一道面试题:StringBuffer a=new StringBuffer ("A"); StringBuffer b=new StringBuffer
前几天又看到这个面试题,再次看看 public class Jtest{ public static void main(String[] args) { StringBuffer a=new Str ...
- <script>document.write(location.href)</script>
<script>document.write(location.href)</script> 什么意思?
- POJ1815_Friendship
一个无向图,问你删除多少点后,可以隔断起点到终点的所有路径?输出字典序最小的删点方案. 求最小点割,先拆点,容量为1,普通边容量无穷,最大流即为应删点数. 需要求出字典序最小的方案,可以从小到大枚举所 ...
- HDU3046_Pleasant sheep and big big wolf
给一个n*m的数字阵,1表示羊的位置,2表示狼的位置,0表示没有东西,可以通过.在每个格子的4边都可以建立围栏,有围栏的话狼是不能通过的. 现在求最少建立多少围栏能够保证狼无法接触到羊. 题目的模型很 ...
- Java 基础--小结
Java 基础--小结 java基础 Java源程序(.java文件)——>java字节码文件(.class文件)——>由解释执行器(java.exe)将字节码文件加载到java虚拟机( ...
- 【Java并发编程】之五:volatile变量修饰符—意料之外的问题
volatile用处说明 在JDK1.2之前,Java的内存模型实现总是从主存(即共享内存)读取变量,是不需要进行特别的注意的.而随着JVM的成熟和优化,现在在多线程环境下volatile关键字的 ...
- BZOJ3504 CQOI2014危桥(最大流)
如果只有一个人的话很容易想到最大流,正常桥连限流inf双向边,危桥连限流2双向边即可.现在有两个人,容易想到给两起点建超源两汇点建超汇,但这样没法保证两个人各自到达自己要去的目的地.于是再超源连一个人 ...
- MT【150】源自斐波那契数列
(清华2017.4.29标准学术能力测试7) 已知数列$\{x_n\}$,其中$x_1=a$,$x_2=b$,$x_{n+1}=x_n+x_{n-1}$($a,b$是正整数),若$2008$为数列中的 ...
- Linux中的防火墙----iptables
防火墙,它是一种位于内部网络与外部网络之间的网络安全系统.一项信息安全的防护系统,依照特定的规则,允许或是限制传输的数据通过. 防火墙根据主要的功能可分为网络层防火墙.应用层防火墙.数据库防火墙. 网 ...
- vim 折叠的用法
http://www.cnblogs.com/fakis/archive/2011/04/14/2016213.html 1. 折叠方式 可用选项来设定折叠方式: 可在Vim 配置文件中设置 set ...