编写一个diff工具,用于判断两个目录下所有的改动 --1.0版本
需求说明:
编写一个diff工具,用于判断两个目录下所有的改动
详细介绍:
- 有A和B两个目录,目录所在位置及层级均不确定
- 需要以B为基准找出两个目录中所有有改动的文件(文件或内容增加、修改、删除),将有改动的文件放入第三个目录中,层级结构与原目录相同
- 将所有新增与更新信息记录到更新日志文件中
- 将删除信息单独记录到删除日志文件中
- 每次执行diff工具需要生成一个新的以日期命名的目录存放文件
使用场景:
本工具用于软件版本升级时找出两个版本间所有修改过的文件,便于增量替换。
提示:
- 使用CRC判断文件是否改动
- 控制台日志记录使用slf4j,增删日志可以使用其他方式记录
- 部分jar包已提供,如需使用到新的jar包可自行添加
1 package comcollection.test; 2 3 import java.io.BufferedInputStream; 4 import java.io.File; 5 import java.io.FileInputStream; 6 import java.io.FileOutputStream; 7 import java.io.IOException; 8 import java.nio.channels.FileChannel; 9 import java.text.SimpleDateFormat; 10 import java.util.ArrayList; 11 import java.util.Date; 12 import java.util.HashMap; 13 import java.util.List; 14 import java.util.Map; 15 import java.util.zip.CRC32; 16 import org.apache.commons.io.FileUtils; 17 18 /** 19 * @author MJC 2018年4月24日 上午23:53:45 20 */ 21 public class DiffUtils { 22 23 private static void writeToFile(File file, String content, String charbyte) { 24 try { 25 FileUtils.write(file, content + "\n", "UTF-8", true); 26 } catch (IOException e) { 27 e.printStackTrace(); 28 } 29 } 30 31 @SuppressWarnings("resource") 32 public static void judgeFileChange(String oldUrl, String newUrl, String diffUrl) { 33 34 FileChannel inputChannel = null; 35 FileChannel outputChannel = null; 36 HashMap<String, File> filesMap = getFilesUpdateAndAddMap(oldUrl, newUrl); 37 38 // 创建diffUrl对应的根目录 39 File fileDir = new File(diffUrl); 40 fileDir.mkdirs(); 41 42 Date now = new Date(); 43 SimpleDateFormat dateFormat = new SimpleDateFormat("yyyy年MM月dd日HH点mm分ss秒"); 44 String nowTime = dateFormat.format(now); 45 diffUrl = diffUrl + "\\" + nowTime + "\\"; 46 File fileDire = new File(diffUrl); 47 fileDire.mkdirs(); 48 49 // 创建delete.log、updateAndAdd.log日志文件 50 String deleteLog = diffUrl + "delete.log"; 51 String updateAndAddLog = diffUrl + "updateAndAdd.log"; 52 File deleteLogFile = null; 53 File updateAndAddLogFile = null; 54 deleteLogFile = new File(deleteLog); 55 updateAndAddLogFile = new File(updateAndAddLog); 56 57 // 遍历删除文件、并且将信息写入日志文件 58 String timeUrl = "-------- Diff时间:" + nowTime + " --------"; 59 writeToFile(deleteLogFile, timeUrl, "UTF-8"); 60 List<String> oldUrlFilePaths = getAllFilePaths(oldUrl); 61 List<String> newUrlFilePaths = getAllFilePaths(newUrl); 62 int count = 0; 63 long start = System.currentTimeMillis(); // 获取开始时间 64 for (String filePath : oldUrlFilePaths) { 65 if (!newUrlFilePaths.contains(filePath)) { 66 count++; 67 writeToFile(deleteLogFile, filePath, "UTF-8"); 68 } 69 } 70 long end = System.currentTimeMillis(); // 获取结束时间 71 String fileCount = "-------- 共删除文件" + count + "--------"; 72 writeToFile(deleteLogFile, fileCount + "\n", "UTF-8"); 73 String endContent = "-------- 运行完毕,耗时:" + (end - start) + "ms"; 74 writeToFile(deleteLogFile, endContent + "\n", "UTF-8"); 75 76 // 遍历新增/更改文件、并且将信息写入日志文件 77 Date now2 = new Date(); 78 SimpleDateFormat dateFormat2 = new SimpleDateFormat("yyyy年MM月dd日HH点mm分ss秒"); 79 String nowTime2 = dateFormat2.format(now2); 80 String timeUrl2 = "-------- Diff时间:" + nowTime2 + " --------"; 81 writeToFile(updateAndAddLogFile, timeUrl2, "UTF-8"); 82 HashMap<String, Long> oldUrlMap = getAllFileMap(oldUrl); 83 HashMap<String, Long> newUrlMap = getAllFileMap(newUrl); 84 85 for (Map.Entry<String, Long> newUrlMapEntry : newUrlMap.entrySet()) { 86 // 如果元素存在原来的目录下,并且crc相同,就是更新操作 87 if (oldUrlMap.containsKey(newUrlMapEntry.getKey())) { 88 try { 89 long oldUrlCrc = oldUrlMap.get(newUrlMapEntry.getKey()); 90 long newUrlCrc = newUrlMapEntry.getValue(); 91 if (oldUrlCrc != newUrlCrc) { 92 writeToFile(updateAndAddLogFile, "更新:" + newUrlMapEntry.getKey(), "UTF-8"); 93 } 94 } catch (Exception e) { 95 e.printStackTrace(); 96 } 97 98 } else {// 如果元素不存在A目录下,就是新增的元素 99 writeToFile(updateAndAddLogFile, "新增:" + newUrlMapEntry.getKey(), "UTF-8"); 100 } 101 } 102 /** 103 * 将新增、更新的文件copy到diffUrl目录下 104 */ 105 for (Map.Entry<String, File> addAndUpdate : filesMap.entrySet()) { 106 // 创建每个文件对应的目录 107 File file = addAndUpdate.getValue(); 108 String filePath = diffUrl + addAndUpdate.getKey(); 109 String fileDirs = filePath.replace(file.getName(), ""); 110 File creFileDir = new File(fileDirs); 111 creFileDir.mkdirs(); 112 113 try { 114 inputChannel = new FileInputStream(file).getChannel(); 115 outputChannel = new FileOutputStream(new File(filePath)).getChannel(); 116 outputChannel.transferFrom(inputChannel, 0, inputChannel.size()); 117 } catch (Exception e) { 118 e.printStackTrace(); 119 } finally { 120 try { 121 inputChannel.close(); 122 } catch (IOException e) { 123 e.printStackTrace(); 124 } 125 try { 126 outputChannel.close(); 127 } catch (IOException e) { 128 e.printStackTrace(); 129 } 130 } 131 } 132 } 133 134 /** 135 * 获取新增/更新文件-用于遍历出file文件,以便复制操作引用此方法 136 * 137 * @param oldUrl 138 * @param newUrl 139 * @return 140 */ 141 private static HashMap<String, File> getFilesUpdateAndAddMap(String oldUrl, String newUrl) { 142 HashMap<String, File> oldUrlMap = getAllFileMaps(oldUrl); 143 HashMap<String, File> newUrlMap = getAllFileMaps(newUrl); 144 HashMap<String, File> addAndUpdateMap = new HashMap<String, File>(); 145 for (Map.Entry<String, File> newUrlEntry : newUrlMap.entrySet()) { 146 // 如果元素存在A目录下,且crc相同,就是更新 147 if (oldUrlMap.containsKey(newUrlEntry.getKey())) { 148 try { 149 long oldFileCrc = getFileCRC(oldUrlMap.get(newUrlEntry.getKey())); 150 long newFileCrc = getFileCRC(newUrlEntry.getValue()); 151 if (oldFileCrc != newFileCrc) { 152 addAndUpdateMap.put(newUrlEntry.getKey(), newUrlEntry.getValue()); 153 } 154 } catch (Exception e) { 155 e.printStackTrace(); 156 } 157 158 } else {// 如果元素不存在A目录下,就是新增的元素 159 addAndUpdateMap.put(newUrlEntry.getKey(), newUrlEntry.getValue()); 160 } 161 } 162 return addAndUpdateMap; 163 } 164 165 /** 166 * 遍历所有文件-- map的value类型为File形式 167 * 168 * @param path 169 * @return 170 */ 171 private static HashMap<String, File> getAllFileMaps(String path) { 172 173 List<File> allFileList = new ArrayList<File>(); 174 HashMap<String, File> resMap = new HashMap<String, File>(); 175 176 allFileList = getAllFile(new File(path), allFileList); 177 for (File file : allFileList) { 178 resMap.put(file.getAbsolutePath().replace(path, ""), file); 179 } 180 return resMap; 181 } 182 183 /** 184 * 遍历所有文件-- map的value类型为long形式的CRC 该路径去掉url前缀 185 * 186 * @param path 187 * @return 188 */ 189 private static HashMap<String, Long> getAllFileMap(String url) { 190 191 List<File> allFileList = new ArrayList<File>(); 192 HashMap<String, Long> resMap = new HashMap<String, Long>(); 193 194 allFileList = getAllFile(new File(url), allFileList); 195 for (File file : allFileList) { 196 resMap.put(file.getAbsolutePath().replace(url, ""), getFileCRC(file)); 197 } 198 return resMap; 199 } 200 201 /** 202 * 遍历该目录下的所有文件对应的路径(截取 path之后的路径) 203 * 204 * @param path 205 * @return 206 */ 207 private static List<String> getAllFilePaths(String path) { 208 209 List<File> allFileList = new ArrayList<File>(); 210 List<String> allFilePaths = new ArrayList<String>(); 211 212 allFileList = getAllFile(new File(path), allFileList); 213 for (File file : allFileList) { 214 allFilePaths.add(file.getAbsolutePath().replace(path, "")); 215 } 216 217 return allFilePaths; 218 } 219 220 /** 221 * 遍历该目录下的所有文件 222 * 223 * @param file 224 * @param allFileList 225 * @return 226 */ 227 private static List<File> getAllFile(File file, List<File> allFileList) { 228 if (file.exists()) { 229 // 如果当前对象是文件夹,递归调用此方法,获取目录下的所有文件 230 if (file.isDirectory()) { 231 File f[] = file.listFiles(); 232 for (File tempFile : f) { 233 getAllFile(tempFile, allFileList); 234 } 235 } else { 236 // 如果当前对象是个文件,将文件存到list中 237 allFileList.add(file); 238 } 239 } 240 return allFileList; 241 } 242 243 /** 244 * 获取文件的CRC 245 * 246 * @param file 247 * @return 248 * @throws IOException 249 */ 250 private static long getFileCRC(File file) { 251 BufferedInputStream bsrc = null; 252 CRC32 crc = new CRC32(); 253 try { 254 bsrc = new BufferedInputStream(new FileInputStream(file)); 255 byte[] bytes = new byte[1024]; 256 int i; 257 while ((i = bsrc.read(bytes)) != -1) { 258 crc.update(bytes, 0, i); 259 } 260 } catch (Exception e) { 261 262 } finally { 263 if (bsrc != null) { 264 try { 265 bsrc.close(); 266 } catch (IOException e) { 267 e.printStackTrace(); 268 } 269 } 270 } 271 return crc.getValue(); 272 } 273 }
1 package comcollection.test; 2 3 4 import org.junit.Test; 5 6 /** 7 * @author MJC 2018年4月24日 上午23:53:45 8 */ 9 public class DiffUtilsTest { 10 private static final String oldUrl = "C:\\Users\\jljd\\Desktop\\diff工具需求说明及示例\\diff工具需求说明及示例\\A"; 11 private static final String newUrl = "C:\\Users\\jljd\\Desktop\\diff工具需求说明及示例\\diff工具需求说明及示例\\B"; 12 private static final String diffUrl = "C:\\Users\\jljd\\Desktop\\diff工具需求说明及示例\\diff工具需求说明及示例\\change"; 13 14 @Test 15 public void testjudgeFileChange(){ 16 DiffUtils7.judgeFileChange(oldUrl, newUrl, diffUrl); 17 } 18 19 }
纸上学来终觉浅,觉知此事需躬行