需求:
編寫一個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);