Java: 如何在一个文件夹中不仅按名称搜索重复文件,而且按大小和内容搜索?
Java: How to search duplicate files in a folder not only by name ,but also by size and content?
我想创建一个 Java 应用程序来识别重复项。到目前为止,我只能通过名称找到重复项,但我还需要大小、文件类型,也许还需要内容。到目前为止,这是我的代码,使用 HashMap
:
public static void find(Map<String, List<String>> lists, File dir) {
for (File f : dir.listFiles()) {
if (f.isDirectory()) {
find(lists, f);
} else {
String hash = f.getName() + f.length();
List<String> list = lists.get(hash);
if (list == null) {
list = new LinkedList<String>();
lists.put(hash, list);
}
list.add(f.getAbsolutePath());
}
}
}
如果 2 个文件具有相同的扩展名和相同的文件大小,则认为这两个文件相等,只需创建一个表示此 'equality' 的对象即可。所以,你会做这样的事情:
public class FileEquality {
private final String fileExtension;
private final long fileSize;
// constructor, toString, equals, hashCode, and getters here.
}
(并填写所有缺失的样板文件:Constructor、toString、equals、hashCode 和 getter。如果愿意,请参阅 Project Lombok's @Value 以简化此操作)。您可以使用 fileName.lastIndexOf('.')
和 fileName.substring(lastIndex)
从文件名中获取文件扩展名。使用 lombok,你只需要写:
@lombok.Value public class FileEquality {
String fileExtension;
long fileSize;
}
然后使用 FileEquality
对象作为哈希图中的键而不是字符串。但是,仅仅因为您有 'foo.txt' 和 'bar.txt' 两者恰好都是 500 字节大小并不意味着这 2 个文件是重复的。因此,您也希望包含内容,但是,如果您扩展 FileEquality
class 以包含文件的内容,则会出现两件事:
如果您仍然要检查内容,大小和文件扩展名有什么关系?如果 foo.txt
和 bar.jpg
的内容完全相同,那么它们就是重复的,不是吗?何苦。您可以将内容作为 byte[]
传达,但请注意,编写适当的 hashCode()
和 equals()
实现(如果您想将此对象用作哈希映射的键,则需要这样做)成为有点棘手。幸运的是,lombok 的 @Value
会做对,所以我建议你使用它。
这意味着文件内容的 全部 都在您的 JVM 进程内存中。除非您正在检查非常小的文件,否则您只会 运行 内存不足。您可以通过不存储文件的全部内容,而是存储内容的散列来稍微抽象一下。 Google 关于如何在 java 中计算文件的 sha-256 散列。将此哈希值放入您的 FileEquality
中,现在您可以避免内存问题。理论上可能有 2 个文件具有不同的内容,但它们哈希到完全相同的 sha-256 值,但这种情况的可能性是天文数字,更重要的是,sha-256 的设计使得故意在数学上不可行制作 2 个这样的文件来扰乱您的应用程序。因此,我建议您只信任散列:)
当然,请注意,对整个文件进行散列处理需要读取整个文件,因此如果您 运行 在包含 500GB 文件的目录中查找重复项,那么您的应用程序将需要最低读取500GB,需要一定时间
我很久以前就做了这个应用程序,如果你想学习的话,我找到了它的一些源代码。
此方法通过比较两个文件字节来工作。
public static boolean checkBinaryEquality(File file1, File file2) {
if(file1.length() != file2.length()) return false;
try(FileInputStream f1 = new FileInputStream(file1); FileInputStream f2 = new FileInputStream(file2)){
byte bus1[] = new byte[1024],
bus2[] = new byte[1024];
// comparing files bytes one by one if we found unmatched results that means they are not equal
while((f1.read(bus1)) >= 0) {
f2.read(bus2);
for(int i = 0; i < 1024;i++)
if(bus1[i] != bus2[i])
return false;
}
// passed
return true;
} catch (IOException exp) {
// problems occurred so let's consider them not equal
return false;
}
}
将此方法与名称和扩展名检查相结合,您就可以开始了。
我使用 MessageDigest 并检查了一些文件,并根据我在标题和描述中列出的所有标准找到了重复项。谢谢大家
private static MessageDigest messageDigest;
static {
try {
messageDigest = MessageDigest.getInstance("SHA-512");
} catch (NoSuchAlgorithmException e) {
throw new RuntimeException("cannot initialize SHA-512 hash function", e);
}
}
这是执行重复搜索代码后的结果
public static void find(Map<String, List<String>> lists, File dir) {
for (File f : dir.listFiles()) {
if (f.isDirectory()) {
find(lists, f);
} else {
try{
FileInputStream fi = new FileInputStream(f);
byte fileData[] = new byte[(int) f.length()];
fi.read(fileData);
fi.close();
//Crearea id unic hash pentru fisierul curent
String hash = new BigInteger(1, messageDigest.digest(fileData)).toString(16);
List<String> list = lists.get(hash);
if (list == null) {
list = new LinkedList<String>();
}
//Adăugați calea către listă
list.add(f.getAbsolutePath());
//Adauga lista actualizată la tabelul Hash
lists.put(hash, list);
}catch (IOException e) {
throw new RuntimeException("cannot read file " + f.getAbsolutePath(), e);
}
}
}
}
copy-paste-example
创建一个扩展 File
的 class
import java.io.File;
import java.io.FileInputStream;
import java.io.IOException;
import java.util.Arrays;
public class MyFile extends File {
private static final long serialVersionUID = 1L;
public MyFile(final String pathname) {
super(pathname);
}
@Override
public boolean equals(final Object obj) {
if (this == obj) {
return true;
}
if (this.getClass() != obj.getClass()) {
return false;
}
final MyFile other = (MyFile) obj;
if (!Arrays.equals(this.getContent(), other.getContent())) {
return false;
}
if (this.getName() == null) {
if (other.getName() != null) {
return false;
}
} else if (!this.getName().equals(other.getName())) {
return false;
}
if (this.length() != other.length()) {
return false;
}
return true;
}
@Override
public int hashCode() {
final int prime = 31;
int result = prime;
result = (prime * result) + Arrays.hashCode(this.getContent());
result = (prime * result) + ((this.getName() == null) ? 0 : this.getName().hashCode());
result = (prime * result) + (int) (this.length() ^ (this.length() >>> 32));
return result;
}
private byte[] getContent() {
try (final FileInputStream fis = new FileInputStream(this)) {
return fis.readAllBytes();
} catch (final IOException e) {
e.printStackTrace();
return new byte[] {};
}
}
}
读取基本目录
import java.io.File;
import java.util.HashMap;
import java.util.Iterator;
import java.util.List;
import java.util.Map;
import java.util.Map.Entry;
import java.util.Vector;
public class FileTest {
public FileTest() {
super();
}
public static void main(final String[] args) {
final Map<MyFile, List<MyFile>> duplicates = new HashMap<>();
FileTest.handleDirectory(duplicates, new File("[path to base directory]"));
final Iterator<Entry<MyFile, List<MyFile>>> iterator = duplicates.entrySet().iterator();
while (iterator.hasNext()) {
final Entry<MyFile, List<MyFile>> next = iterator.next();
if (next.getValue().size() == 0) {
iterator.remove();
} else {
System.out.println(next.getKey().getName() + " - " + next.getKey().getAbsolutePath());
for (final MyFile file : next.getValue()) {
System.out.println(" ->" + file.getName() + " - " + file.getAbsolutePath());
}
}
}
}
private static void handleDirectory(final Map<MyFile, List<MyFile>> duplicates, final File directory) {
final File dir = directory;
if (dir.isDirectory()) {
final File[] files = dir.listFiles();
for (final File file : files) {
if (file.isDirectory()) {
FileTest.handleDirectory(duplicates, file);
continue;
}
final MyFile myFile = new MyFile(file.getAbsolutePath());
if (!duplicates.containsKey(myFile)) {
duplicates.put(myFile, new Vector<>());
} else {
duplicates.get(myFile).add(myFile);
}
}
}
}
}
我想创建一个 Java 应用程序来识别重复项。到目前为止,我只能通过名称找到重复项,但我还需要大小、文件类型,也许还需要内容。到目前为止,这是我的代码,使用 HashMap
:
public static void find(Map<String, List<String>> lists, File dir) {
for (File f : dir.listFiles()) {
if (f.isDirectory()) {
find(lists, f);
} else {
String hash = f.getName() + f.length();
List<String> list = lists.get(hash);
if (list == null) {
list = new LinkedList<String>();
lists.put(hash, list);
}
list.add(f.getAbsolutePath());
}
}
}
如果 2 个文件具有相同的扩展名和相同的文件大小,则认为这两个文件相等,只需创建一个表示此 'equality' 的对象即可。所以,你会做这样的事情:
public class FileEquality {
private final String fileExtension;
private final long fileSize;
// constructor, toString, equals, hashCode, and getters here.
}
(并填写所有缺失的样板文件:Constructor、toString、equals、hashCode 和 getter。如果愿意,请参阅 Project Lombok's @Value 以简化此操作)。您可以使用 fileName.lastIndexOf('.')
和 fileName.substring(lastIndex)
从文件名中获取文件扩展名。使用 lombok,你只需要写:
@lombok.Value public class FileEquality {
String fileExtension;
long fileSize;
}
然后使用 FileEquality
对象作为哈希图中的键而不是字符串。但是,仅仅因为您有 'foo.txt' 和 'bar.txt' 两者恰好都是 500 字节大小并不意味着这 2 个文件是重复的。因此,您也希望包含内容,但是,如果您扩展 FileEquality
class 以包含文件的内容,则会出现两件事:
如果您仍然要检查内容,大小和文件扩展名有什么关系?如果
foo.txt
和bar.jpg
的内容完全相同,那么它们就是重复的,不是吗?何苦。您可以将内容作为byte[]
传达,但请注意,编写适当的hashCode()
和equals()
实现(如果您想将此对象用作哈希映射的键,则需要这样做)成为有点棘手。幸运的是,lombok 的@Value
会做对,所以我建议你使用它。这意味着文件内容的 全部 都在您的 JVM 进程内存中。除非您正在检查非常小的文件,否则您只会 运行 内存不足。您可以通过不存储文件的全部内容,而是存储内容的散列来稍微抽象一下。 Google 关于如何在 java 中计算文件的 sha-256 散列。将此哈希值放入您的
FileEquality
中,现在您可以避免内存问题。理论上可能有 2 个文件具有不同的内容,但它们哈希到完全相同的 sha-256 值,但这种情况的可能性是天文数字,更重要的是,sha-256 的设计使得故意在数学上不可行制作 2 个这样的文件来扰乱您的应用程序。因此,我建议您只信任散列:)
当然,请注意,对整个文件进行散列处理需要读取整个文件,因此如果您 运行 在包含 500GB 文件的目录中查找重复项,那么您的应用程序将需要最低读取500GB,需要一定时间
我很久以前就做了这个应用程序,如果你想学习的话,我找到了它的一些源代码。
此方法通过比较两个文件字节来工作。
public static boolean checkBinaryEquality(File file1, File file2) {
if(file1.length() != file2.length()) return false;
try(FileInputStream f1 = new FileInputStream(file1); FileInputStream f2 = new FileInputStream(file2)){
byte bus1[] = new byte[1024],
bus2[] = new byte[1024];
// comparing files bytes one by one if we found unmatched results that means they are not equal
while((f1.read(bus1)) >= 0) {
f2.read(bus2);
for(int i = 0; i < 1024;i++)
if(bus1[i] != bus2[i])
return false;
}
// passed
return true;
} catch (IOException exp) {
// problems occurred so let's consider them not equal
return false;
}
}
将此方法与名称和扩展名检查相结合,您就可以开始了。
我使用 MessageDigest 并检查了一些文件,并根据我在标题和描述中列出的所有标准找到了重复项。谢谢大家
private static MessageDigest messageDigest;
static {
try {
messageDigest = MessageDigest.getInstance("SHA-512");
} catch (NoSuchAlgorithmException e) {
throw new RuntimeException("cannot initialize SHA-512 hash function", e);
}
}
这是执行重复搜索代码后的结果
public static void find(Map<String, List<String>> lists, File dir) {
for (File f : dir.listFiles()) {
if (f.isDirectory()) {
find(lists, f);
} else {
try{
FileInputStream fi = new FileInputStream(f);
byte fileData[] = new byte[(int) f.length()];
fi.read(fileData);
fi.close();
//Crearea id unic hash pentru fisierul curent
String hash = new BigInteger(1, messageDigest.digest(fileData)).toString(16);
List<String> list = lists.get(hash);
if (list == null) {
list = new LinkedList<String>();
}
//Adăugați calea către listă
list.add(f.getAbsolutePath());
//Adauga lista actualizată la tabelul Hash
lists.put(hash, list);
}catch (IOException e) {
throw new RuntimeException("cannot read file " + f.getAbsolutePath(), e);
}
}
}
}
copy-paste-example
创建一个扩展
的 classFile
import java.io.File; import java.io.FileInputStream; import java.io.IOException; import java.util.Arrays; public class MyFile extends File { private static final long serialVersionUID = 1L; public MyFile(final String pathname) { super(pathname); } @Override public boolean equals(final Object obj) { if (this == obj) { return true; } if (this.getClass() != obj.getClass()) { return false; } final MyFile other = (MyFile) obj; if (!Arrays.equals(this.getContent(), other.getContent())) { return false; } if (this.getName() == null) { if (other.getName() != null) { return false; } } else if (!this.getName().equals(other.getName())) { return false; } if (this.length() != other.length()) { return false; } return true; } @Override public int hashCode() { final int prime = 31; int result = prime; result = (prime * result) + Arrays.hashCode(this.getContent()); result = (prime * result) + ((this.getName() == null) ? 0 : this.getName().hashCode()); result = (prime * result) + (int) (this.length() ^ (this.length() >>> 32)); return result; } private byte[] getContent() { try (final FileInputStream fis = new FileInputStream(this)) { return fis.readAllBytes(); } catch (final IOException e) { e.printStackTrace(); return new byte[] {}; } } }
读取基本目录
import java.io.File; import java.util.HashMap; import java.util.Iterator; import java.util.List; import java.util.Map; import java.util.Map.Entry; import java.util.Vector; public class FileTest { public FileTest() { super(); } public static void main(final String[] args) { final Map<MyFile, List<MyFile>> duplicates = new HashMap<>(); FileTest.handleDirectory(duplicates, new File("[path to base directory]")); final Iterator<Entry<MyFile, List<MyFile>>> iterator = duplicates.entrySet().iterator(); while (iterator.hasNext()) { final Entry<MyFile, List<MyFile>> next = iterator.next(); if (next.getValue().size() == 0) { iterator.remove(); } else { System.out.println(next.getKey().getName() + " - " + next.getKey().getAbsolutePath()); for (final MyFile file : next.getValue()) { System.out.println(" ->" + file.getName() + " - " + file.getAbsolutePath()); } } } } private static void handleDirectory(final Map<MyFile, List<MyFile>> duplicates, final File directory) { final File dir = directory; if (dir.isDirectory()) { final File[] files = dir.listFiles(); for (final File file : files) { if (file.isDirectory()) { FileTest.handleDirectory(duplicates, file); continue; } final MyFile myFile = new MyFile(file.getAbsolutePath()); if (!duplicates.containsKey(myFile)) { duplicates.put(myFile, new Vector<>()); } else { duplicates.get(myFile).add(myFile); } } } } }