一个diff工具,用于判断两个目录下所有的改动(比较新旧版本文件夹)

Posted qlqwjy

tags:

篇首语:本文由小常识网(cha138.com)小编为大家整理,主要介绍了一个diff工具,用于判断两个目录下所有的改动(比较新旧版本文件夹)相关的知识,希望对你有一定的参考价值。

需求:

  编写一个diff工具,用于判断两个目录下所有的改动

 

详细介绍:

  1. 有A和B两个目录,目录所在位置及层级均不确定
  2. 需要以B为基准找出两个目录中所有有改动的文件(文件或内容增加、修改、删除),将有改动的文件放入第三个目录中,层级结构与原目录相同
  3. 将所有新增与更新信息记录到更新日志文件中
  4. 将删除信息单独记录到删除日志文件中
  5. 每次执行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() + "----
", "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("删除文件	" + oldKey);
                try {
                    FileUtils.write(deleteLogFile, "删除文件	" + oldKey + "
",
                            "UTF-8", true);
                } catch (IOException e) {
                    logger.error("将删除日志写入文件deteFile.log出错", e);
                }
                deleteFileNames.add(oldKey);
            }
        }
        
        try {
            FileUtils.write(deleteLogFile, "
", "UTF-8", true);
            FileUtils.write(deleteLogFile, "---------删除文件日志结束:共删除"
                    + deleteFileNames.size() + "个文件----" + "
", "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() + "----" + "
", "UTF-8",
                    true);
            FileUtils.write(addUpdateLogFile, "
", "UTF-8", true);
            FileUtils.write(addUpdateLogFile, "----共新增文件" + addFileNames.size()
                    + "个----
", "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
                        + "
", "UTF-8", true);
            } catch (IOException e) {
                logger.error("将新增信息写入文件addAndUpdate.log出错", e);
            }
        }

        // 4.2更新信息写入日志
        try {
            FileUtils.write(addUpdateLogFile, "
", "UTF-8", true);
            FileUtils.write(addUpdateLogFile,
                    "----共更新文件" + updateFileNames.size() + "个----
",
                    "UTF-8", true);
            logger.info("----共更新文件" + updateFileNames.size() + "个----");
        } catch (IOException e) {
            logger.error("将更新信息写入文件addAndUpdate.log出错", e);
        }
        for (String updateFileName : updateFileNames) {
            try {
                FileUtils.write(addUpdateLogFile, "更新了文件" + updateFileName
                        + "
", "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----" + "
", "UTF-8", true);
            FileUtils.write(deleteLogFile, "----运行结束,耗时"
                    + (endTime - startTime) + "ms----" + "
", "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:UsersliqiangDesktop新建文件件夹考核1 C:UsersliqiangDesktop新建文件夹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工具,用于判断两个目录下所有的改动(比较新旧版本文件夹)的主要内容,如果未能解决你的问题,请参考以下文章

diff命令和patch命令的使用

linux每日命令(33):diff命令

linux命令-diff对比文件工具

linux diff 命令

每天一个linux命令(36):diff 命令

linux学习笔记22---命令diff和diff3