⼀个diff⼯具,⽤于判断两个⽬录下所有的改动(⽐较新旧版
本⽂件夹)
需求:
  编写⼀个diff⼯具,⽤于判断两个⽬录下所有的改动
详细介绍:
1. 有A和B两个⽬录,⽬录所在位置及层级均不确定
2. 需要以B为基准出两个⽬录中所有有改动的⽂件(⽂件或内容增加、修改、删除),将有改动的⽂件放⼊第三个⽬录中,层
级结构与原⽬录相同
3. 将所有新增与更新信息记录到更新⽇志⽂件中
4. 将删除信息单独记录到删除⽇志⽂件中
5. 每次执⾏diff⼯具需要⽣成⼀个新的以⽇期命名的⽬录存放⽂件
使⽤场景:
  本⼯具⽤于软件版本升级时出两个版本间所有修改过的⽂件,便于增量替换。
提⽰:使⽤CRC判断⽂件是否改动
依赖的Jar包:
代码如下:
package test2;
import java.io.File;
import java.io.IOException;
SimpleDateFormat;
import java.util.ArrayList;
import java.util.Date;
import java.util.HashMap;
import java.util.List;
import java.util.Map;
import org.apachemons.io.FileUtils;
import org.slf4j.Logger;
import org.slf4j.LoggerFactory;
public class DiffUtil {
private static Logger logger = Logger(DiffUtil.class);// slf4j的⽇志记录器
/**
* 对⽐⽂件
* @param oldDir    旧版本⽂件(需求中的A⽂件夹)
* @param nowDir    新版本⽂件(需求中的B⽂件夹)
* @param diffDir  ⽣成对⽐结果的⽂件夹(需求中的change⽂件夹)
*/
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) {
<("将删除⽇志写⼊⽂件deteFile.log出错", e);
}
List<String> deleteFileNames = new ArrayList<String>();
for (String oldKey : oldFileCRCs.keySet()) {
if (!ainsKey(oldKey)) {
logger.info("删除⽂件\t" + oldKey);
try {
FileUtils.write(deleteLogFile, "删除⽂件\t" + oldKey + "\r\n",
"UTF-8", true);
} catch (IOException e) {
<("将删除⽇志写⼊⽂件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) {
<("将删除⽇志的统计信息写⼊⽂件deteFile.log出错", e);
}log4j2版本
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 (!ainsKey(nowKey)) {
addFileNames.add(nowKey);
} else {
if ((nowKey).(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) {
<("将新增信息写⼊⽂件addAndUpdate.log出错", e1);
}
for (String addFileName : addFileNames) {
try {
logger.info("增加了⽂件" + addFileName);
FileUtils.write(addUpdateLogFile, "增加了⽂件" + addFileName
+ "\r\n", "UTF-8", true);
} catch (IOException e) {
<("将新增信息写⼊⽂件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) {
<("将更新信息写⼊⽂件addAndUpdate.log出错", e);
}
for (String updateFileName : updateFileNames) {
try {
FileUtils.write(addUpdateLogFile, "更新了⽂件" + updateFileName
+ "\r\n", "UTF-8", true);
logger.info("更新了⽂件" + updateFileName);
} catch (IOException e) {
<("将更新信息写⼊⽂件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) {
<("将运⾏耗时写⼊⽇志⽂件出错", 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 = Name();
destFileDir = new File((diffDir + "\\" + sourceFileName).replace(
fileName, ""));
destFileDir.mkdirs();
try {
} catch (IOException e) {
<("复制⽂件出错",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 (!ists()) {// ⽂件不存在直接返回
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) {
<("获取⽂件的CRC32出错",e);
}
resultMap.AbsolutePath().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];*/
DiffUtilpareFile(oldDir, nowDir, diffDir);
}
}
我已经将此⼯具作为⼀个jar包打包起来,下载地址:
运⾏⽅式:
java -jar DiffUtil.jar C:\Users\liqiang\Desktop\新建⽂件件夹\考核1 C:\Users\liqiang\Desktop\新建⽂件夹\change
总结:
  1. ⽂件复制有多种⽅式,可以⽤    pyFile(srcFile, destFile);  只需要传递两个File参数,第⼀个是源⽂件,第⼆个是⽬的⽂件。
              也可以⽤  py(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 = ad(bytes)) != -1) {
crc.update(bytes, 0, i);