【问题标题】:Java: How to search duplicate files in a folder not only by name ,but also by size and content?Java:如何不仅按名称,而且按大小和内容搜索文件夹中的重复文件?
【发布时间】:2019-11-13 03:18:00
【问题描述】:

我想创建一个 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());
        }
    }
}

【问题讨论】:

  • 也许this question 会有所帮助
  • 您是否尝试过引入一些 FileHeader 类,在其中使用所有对您而言重要的元素?基于此类输出一些校验和 - sha 或 md5 并将其用作 Collectors.grouping() 中的键?

标签: java hashmap duplicates size


【解决方案1】:

如果两个文件具有相同的扩展名和相同的文件大小,则认为它们相等,只需创建一个表示这种“相等”的对象即可。所以,你会做这样的事情:

public class FileEquality {
    private final String fileExtension;
    private final long fileSize;

    // constructor, toString, equals, hashCode, and getters here.
}

(并填写所有缺少的样板:Constructor、toString、equals、hashCode 和 getters。如果您愿意,请参阅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 字节,并不意味着这两个文件是重复的。因此,您也希望包含内容,但是,如果您扩展 FileEquality 类以包含文件的内容,则会出现两件事:

  1. 如果您仍然要检查内容,大小和文件扩展名有什么关系?如果foo.txt 和bar.jpg 的内容完全相同,那么它们是重复的,不是吗?何必。您可以将内容传达为byte[],但请注意,编写正确的hashCode() 和equals() 实现(如果您想将此对象用作哈希图的键,则需要这些实现)变得有点棘手。幸运的是,lombok 的 @Value 会做对,所以我建议你使用它。

  2. 这意味着文件内容的全部都在您的 JVM 进程内存中。除非您对非常小的文件进行检查,否则您将耗尽内存。您可以通过不存储文件的全部内容,而是存储内容的哈希来稍微抽象一下。谷歌搜索如何在 java 中计算文件的 sha-256 哈希值。将此哈希值放入您的 FileEquality 中,现在您可以避免内存问题。理论上可能有 2 个内容不同的文件,但它们散列到完全相同的 sha-256 值,但这种可能性是天文数字,更重要的是,sha-256 的设计使得故意在数学上不可行制作 2 个这样的文件来弄乱您的应用程序。因此,我建议您只信任哈希 :)

当然,请注意,散列整个文件需要读取整个文件,因此如果您在包含 500GB 文件的目录上运行重复查找器,那么您的应用程序至少需要读取 500GB ,这需要一些时间。

【讨论】:

  • 如果我使用私有静态MessageDigest messageDigest;静态 { 尝试 { messageDigest = MessageDigest.getInstance("SHA-512"); } catch (NoSuchAlgorithmException e) { throw new RuntimeException("无法初始化 SHA-512 哈希函数", e); } } 和你说的一样吗?
【解决方案2】:

我使用 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);
            }

  }
}

}

【讨论】:

    【解决方案3】:

    我很久以前制作了这个应用程序,如果你想学习的话,我找到了它的一些源代码。

    此方法通过比较两个文件字节来工作。

    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;
        }
    }
    

    将此方法与名称和扩展检查相结合,您就可以开始了。

    【讨论】:

      【解决方案4】:

      复制粘贴示例

      1. 创建一个扩展 File 的类

        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[] {};
                }
            }
        }
        
      2. 读取基础目录

        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);
                        }
                    }
                }
            }
        }
        

      【讨论】:

        猜你喜欢
        • 2017-11-25
        • 2014-07-04
        • 2017-12-21
        • 2015-09-07
        • 1970-01-01
        • 2019-09-13
        • 1970-01-01
        • 2014-08-30
        • 1970-01-01
        相关资源
        最近更新 更多