1.场景描述
前段时间在做项目的过程中遇到这么个情况,一个内网服务器需要从很多台电脑主机的共享文件夹里抓取文件,这个内网服务器还需要将抓取到文件解析并上传到一个平台上,上传到这个平台要保证上传的数据不能重复,由此就需要保证抓取到文件的文件内容不能重复。
2.解决办法
2.1 最开始我的解决办法
最开始用了一个存在很大问题的办法,通过文件名判断文件是否重复,这个办法在正式生产环境中是完全行不通的,因为只要文件名字发生改变但文件内容未发生改变那么服务器也会认为它所抓取到的文件是不重复的,因此这个方法是行不通的。
2.2 通过计算文件的hash值来判断
后来通过查阅资料找到一个办法通过计算文件的hash值来判断两个文件是否相同。这方法是通过计算每个文件的哈希值(例如MD5、SHA-1、SHA-256等)。如果两个文件的哈希值相同,那么它们的内容几乎肯定相同。只需要记录每个被服务器成功抓取到的文件的hash值,并将每次最新抓取到的文件与已经存在的文件的hash值作比较,如果没有存在相同的hash值就保留,存在就移除最新抓取到的。
以下是两两比较的代码:
import java.io.*;
import java.security.MessageDigest;
import java.security.NoSuchAlgorithmException;
/**
* 文件重复检查,用于计算文件的哈希值并判断文件是否重复
*/
public class FileDuplicateChecker {
public static void main(String[] args) {
String filePath1 = "path1"; // 替换为第一个文件的地址
String filePath2 = "path2"; // 替换为第二个文件的地址
// 计算文件的hash值并比较是否重复
try {
String hash1 = calculateFileHash(filePath1); // 计算第一个文件的哈希值
String hash2 = calculateFileHash(filePath2); // 计算第二个文件的哈希值
boolean areFilesDuplicate = hash1.equals(hash2); // 比较哈希值判断文件是否重复
// 输出判断结果
if (areFilesDuplicate) {
System.out.println("两个文件是重复的。");
} else {
System.out.println("两个文件不是重复的。");
}
} catch (NoSuchAlgorithmException | IOException e) {
throw new RuntimeException(e);
}
}
/**
* 计算文件的哈希值
*
* @param filePath 文件路径
* @return 文件的哈希值
* @throws NoSuchAlgorithmException 如果算法不可用
* @throws IOException 如果读取文件时发生错误
*/
private static String calculateFileHash(String filePath) throws NoSuchAlgorithmException, IOException {
MessageDigest md = MessageDigest.getInstance("MD5"); // 获取MD5哈希算法实例
try (FileInputStream fis = new FileInputStream(filePath)) {
byte[] dataBytes = new byte[1024];
int bytesRead;
// 读取文件内容并更新哈希值
while ((bytesRead = fis.read(dataBytes)) != -1) {
md.update(dataBytes, 0, bytesRead);
}
byte[] mdBytes = md.digest(); // 获取哈希值的字节数组
// 将字节数组转换为十六进制表示的哈希值字符串
StringBuilder sb = new StringBuilder();
for (byte mdByte : mdBytes) {
sb.append(Integer.toString((mdByte & 0xff) + 0x100, 16).substring(1));
}
return sb.toString(); // 返回文件的哈希值字符串
}
}
}
以下是将每次计算的文件hash值存入数据库,通过数据库查询判断文件是否重复。
import java.security.MessageDigest;
import java.security.NoSuchAlgorithmException;
import java.sql.Connection;
import java.sql.DriverManager;
import java.sql.PreparedStatement;
import java.sql.ResultSet;
import java.sql.SQLException;
import java.io.*;
/**
* 文件重复检查器,用于计算文件的哈希值并通过数据库查询判断文件是否重复
*/
public class FileDuplicateChecker {
// 数据库连接信息,需要根据实际情况进行修改
private static final String DB_URL = "jdbc:mysql://localhost:3306/database_name";
private static final String DB_USER = "username";
private static final String DB_PASSWORD = "password";
public static void main(String[] args) {
String filePath = "path"; // 替换为新文件的地址
try {
String hash = calculateFileHash(filePath); // 计算新文件的哈希值
// 通过数据库查询判断文件是否重复
boolean isFileDuplicate = checkDuplicateInDatabase(hash);
// 输出判断结果
if (isFileDuplicate) {
System.out.println("文件重复,已存在于数据库中。");
} else {
System.out.println("文件不重复,可以进行上传或保存操作。");
}
} catch (NoSuchAlgorithmException | IOException | SQLException e) {
throw new RuntimeException(e);
}
}
/**
* 计算文件的哈希值
*
* @param filePath 文件路径
* @return 文件的哈希值
* @throws NoSuchAlgorithmException 如果算法不可用
* @throws IOException 如果读取文件时发生错误
*/
private static String calculateFileHash(String filePath) throws NoSuchAlgorithmException, IOException {
MessageDigest md = MessageDigest.getInstance("MD5"); // 获取MD5哈希算法实例
try (FileInputStream fis = new FileInputStream(filePath)) {
byte[] dataBytes = new byte[1024];
int bytesRead;
// 读取文件内容并更新哈希值
while ((bytesRead = fis.read(dataBytes)) != -1) {
md.update(dataBytes, 0, bytesRead);
}
byte[] mdBytes = md.digest(); // 获取哈希值的字节数组
// 将字节数组转换为十六进制表示的哈希值字符串
StringBuilder sb = new StringBuilder();
for (byte mdByte : mdBytes) {
sb.append(Integer.toString((mdByte & 0xff) + 0x100, 16).substring(1));
}
return sb.toString(); // 返回文件的哈希值字符串
}
}
/**
* 通过数据库查询判断文件是否重复
*
* @param fileHash 文件的哈希值
* @return 如果文件重复返回true,否则返回false
* @throws SQLException 如果数据库查询出现错误
*/
private static boolean checkDuplicateInDatabase(String fileHash) throws SQLException {
Connection connection = null;
PreparedStatement preparedStatement = null;
ResultSet resultSet = null;
try {
// 连接数据库
connection = DriverManager.getConnection(DB_URL, DB_USER, DB_PASSWORD);
// 查询数据库中是否存在相同哈希值的文件
String query = "SELECT COUNT(*) FROM files WHERE hash = ?";
preparedStatement = connection.prepareStatement(query);
preparedStatement.setString(1, fileHash);
resultSet = preparedStatement.executeQuery();
// 获取查询结果
if (resultSet.next()) {
int count = resultSet.getInt(1);
return count > 0; // 如果存在记录,说明文件重复
}
} finally {
// 关闭数据库连接
if (resultSet != null) resultSet.close();
if (preparedStatement != null) preparedStatement.close();
if (connection != null) connection.close();
}
return false;
}
}
2.2 逐字节比较
如果哈希值不适用或者不够精确,可以逐字节比较两个文件的内容。这需要读取文件并逐个字节地比较它们,如果文件大小和内容完全相同,那么它们是重复的。
但这个方法不适合文件太多的情况,因为它需要两两进行比较,文件太多会需要太多的时间来进行文件比较。
import java.io.*;
/**
* 逐字节比较文件内容,判断两个文件是否相同
*/
public class ByteByByteFileComparison {
public static void main(String[] args) {
String filePath1 = "path1"; // 文件1的路径
String filePath2 = "path2"; // 文件2的路径
// 调用方法比较两个文件是否内容相同
boolean areFilesEqual = compareFilesByteByByte(filePath1, filePath2);
if (areFilesEqual) {
System.out.println("两个文件内容相同。");
} else {
System.out.println("两个文件内容不同。");
}
}
/**
* 逐字节比较两个文件的内容
*
* @param filePath1 第一个文件路径
* @param filePath2 第二个文件路径
* @return 如果文件内容相同返回true,否则返回false
*/
public static boolean compareFilesByteByByte(String filePath1, String filePath2) {
try (FileInputStream fis1 = new FileInputStream(filePath1);
FileInputStream fis2 = new FileInputStream(filePath2)) {
int byte1, byte2;
// 逐字节比较两个文件内容
while ((byte1 = fis1.read()) != -1 && (byte2 = fis2.read()) != -1) {
if (byte1 != byte2) {
return false; // 发现不相等的字节,文件内容不同
}
}
// 检查文件长度是否相同
return fis1.read() == -1 && fis2.read() == -1;
} catch (IOException e) {
e.printStackTrace();
return false;
}
}
}