软件打包前进行文件去重
首先,直接对软件目录执行 zip 压缩,压缩算法无法解决文件重复的问题,如果软件目录中,重复文件比较多,零散的分布在各个子目录中,那么对目录中的文件进行去重,会节省很多空间。
1. 去重脚本
#!/bin/bash# 检查输入目录
if [ $# -ne 1 ] || [ ! -d "$1" ]; thenecho "用法: $0 <目标目录>"exit 1
fiTARGET_DIR="$1"# 生成文件路径与哈希值的映射(排除目录)
find "$TARGET_DIR" -type f -print0 | xargs -0 md5sum | sort > /tmp/file_hashes.txt# 提取重复哈希值(出现次数 > 1)
awk '{print $1}' /tmp/file_hashes.txt | uniq -d > /tmp/duplicate_hashes.txt# 遍历重复哈希值,保留第一个文件,删除其余重复文件
while read -r hash; do# 查找该哈希值对应的所有文件,按路径排序files=$(grep "^$hash " /tmp/file_hashes.txt | awk '{print $2}' | sort)# 保留第一个文件,删除后续文件first_file=$(echo "$files" | head -n 1)other_files=$(echo "$files" | tail -n +2)if [ -n "$other_files" ]; thenecho "保留: $first_file"echo "删除重复文件: $other_files"rm -f $other_files # 确认无误后删除此行的注释fi
done < /tmp/duplicate_hashes.txtecho "去重完成"
2 去重 Java 代码
import java.io.*;
import java.nio.file.*;
import java.security.MessageDigest;
import java.security.NoSuchAlgorithmException;
import java.util.*;public class RemoveDuplicates {public static void main(String[] args) {if (args.length == 0) {System.out.println("请指定要去重的目录路径");System.out.println("用法: java FileDeduplicator [目录路径]");return;}String directoryPath = "args[0]";File directory = new File(directoryPath);if (!directory.exists() || !directory.isDirectory()) {System.out.println("指定的路径不存在或不是目录");return;}try {// 按文件大小分组,只有大小相同的文件才可能是重复文件Map<Long, List<File>> filesBySize = groupFilesBySize(directory);// 对每个大小组中的文件计算哈希值,找出重复文件Map<String, List<File>> duplicateFiles = findDuplicatesByContent(filesBySize);// 处理重复文件(默认保留一个,删除其他)processDuplicates(duplicateFiles);} catch (IOException e) {System.out.println("处理文件时出错: " + e.getMessage());}}/*** 按文件大小分组*/private static Map<Long, List<File>> groupFilesBySize(File directory) throws IOException {Map<Long, List<File>> filesBySize = new HashMap<>();Files.walk(Paths.get(directory.getAbsolutePath())).filter(Files::isRegularFile).forEach(path -> {try {long size = Files.size(path);File file = path.toFile();filesBySize.computeIfAbsent(size, k -> new ArrayList<>()).add(file);} catch (IOException e) {System.out.println("无法获取文件大小: " + path + " - " + e.getMessage());}});// 移除只有一个文件的分组(不可能是重复文件)filesBySize.values().removeIf(list -> list.size() <= 1);return filesBySize;}/*** 对大小相同的文件计算哈希值,找出内容相同的重复文件*/private static Map<String, List<File>> findDuplicatesByContent(Map<Long, List<File>> filesBySize) throws IOException {Map<String, List<File>> duplicates = new HashMap<>();for (List<File> files : filesBySize.values()) {for (File file : files) {String hash = calculateFileHash(file);duplicates.computeIfAbsent(hash, k -> new ArrayList<>()).add(file);}}// 移除只有一个文件的分组(不是重复文件)duplicates.values().removeIf(list -> list.size() <= 1);return duplicates;}/*** 计算文件的MD5哈希值,用于判断文件内容是否相同*/private static String calculateFileHash(File file) throws IOException {try (InputStream is = new FileInputStream(file)) {MessageDigest md = MessageDigest.getInstance("MD5");byte[] buffer = new byte[8192];int bytesRead;while ((bytesRead = is.read(buffer)) != -1) {md.update(buffer, 0, bytesRead);}byte[] hashBytes = md.digest();// 转换为十六进制字符串StringBuilder sb = new StringBuilder();for (byte b : hashBytes) {sb.append(String.format("%02x", b));}return sb.toString();} catch (NoSuchAlgorithmException e) {// MD5算法是Java标准库自带的,理论上不会抛出此异常throw new RuntimeException("无法获取MD5算法实例", e);}}/*** 处理重复文件,保留第一个,删除其他*/private static void processDuplicates(Map<String, List<File>> duplicateFiles) {if (duplicateFiles.isEmpty()) {System.out.println("未发现重复文件");return;}System.out.println("发现 " + duplicateFiles.size() + " 组重复文件:");int totalDeleted = 0;long size = 0;for (List<File> duplicates : duplicateFiles.values()) {System.out.println("\n重复文件组(共 " + duplicates.size() + " 个):");// 保留第一个文件,删除其他File keepFile = duplicates.get(0);System.out.println("保留: " + keepFile.getAbsolutePath());for (int i = 1; i < duplicates.size(); i++) {File fileToDelete = duplicates.get(i);size+= fileToDelete.length();if (fileToDelete.delete()) {System.out.println("已删除: " + fileToDelete.getAbsolutePath());totalDeleted++;} else {System.out.println("无法删除: " + fileToDelete.getAbsolutePath());}}}System.out.println("\n处理完成,共删除 " + totalDeleted + " 个重复文件");System.out.println("\n处理完成,共删除 " + size/(1024*1024.0) + " MB");}
}
3 使用硬链接
在 Linux 中,硬链接(Hard Link) 是文件系统中同一个文件(数据块)的多个引用。
特性 | 硬链接 | 软链接(符号链接) |
---|---|---|
存储空间 | 不额外占用 | 占用少量空间(存储路径) |
跨文件系统 | 不支持 | 支持 |
链接目录 | 受限(需 root) | 支持 |
原始文件删除后 | 仍可访问(数据保留) | 失效(悬空链接) |
文件类型 | 与原始文件相同 | 特殊文件(l 类型) |
# 创建硬链接
ln 原文件 硬链接名# 查看文件的硬链接数(ls 输出的第二列)
ls -l
因此,在linux环境上,可以使用硬链接来替代删除,然后通过 tar 包进行压缩,以实现节省空间的目的。
tar 可以保留硬链接关系(通过 --hard-dereference 选项控制),再结合 gzip/xz 等压缩。
tar czf archive.tar.gz --hard-dereference dir/ # 压缩时保留硬链接
tar xzf archive.tar.gz # 解压时恢复硬链接