如何有效地输出具有相同内容的文件列表?

Posted

技术标签:

【中文标题】如何有效地输出具有相同内容的文件列表?【英文标题】:How to output the list of files with same content efficiently? 【发布时间】:2017-10-29 07:33:32 【问题描述】:

我得到了一个大小相同的文件列表,我需要返回一个列表列表,其中包含所有具有相同内容的文件。

我的想法是首先将文件散列到一个映射中,其键是 md5 散列值,值是具有键散列值的路径列表。这是hashing()函数的代码:

public static Map<String, List<String>> hashing(List<File> list) throws Exception 
    Map<String, List<String>> map = new HashMap<>();
    for (File f : list) 
        String path = f.getAbsolutePath();
        FileInputStream in = new FileInputStream(path);
        byte[] dataBytes = new byte[1024];

        MessageDigest md = MessageDigest.getInstance("MD5");
        int n = 0;
        while ((n = in.read(dataBytes)) != -1) 
            md.update(dataBytes, 0, n);
        
        byte[] mdBytes = md.digest();

        StringBuilder sb = new StringBuilder();
        for (int i = 0; i < mdBytes.length; i++) 
            sb.append(Integer.toString((mdBytes[i] & 0xff) + 0x100, 16).substring(1));
        
        String hash = sb.toString();
        if (!map.containsKey(hash)) 
            map.put(hash, new ArrayList<>());
        
        map.get(hash).add(path);
    
    return map;

由于两个文件可以散列到相同的值但不同,我想比较具有相同散列值的文件以验证它们是否真的相同。这是checkSame() 函数: 输入List&lt;String&gt; 是具有相同哈希值的文件路径列表,List&lt;List&lt;String&gt;&gt; 是包含具有相同内容的所有文件的列表列表。

public static void checkSame(List<String> list, List<List<String>> result) throws Exception
    List<String> temp = new ArrayList<>();
    for (int i = 1; i < list.size(); i++) 
        if (checkContent(list.get(0), list.get(i))) 
            continue;
        
        list.remove(list.get(i));
        temp.add(list.get(i));
    
    if (list.size() > 1) 
        result.add(list);
    
    if (temp.size() > 1) 
        checkSame(temp, result);
    


public static boolean checkContent (String path1, String path2) throws Exception 
    FileInputStream fis1 = new FileInputStream(path1);
    FileInputStream fis2 = new FileInputStream(path2);
    BufferedReader input1 = new BufferedReader(new InputStreamReader(fis1));
    BufferedReader input2 = new BufferedReader(new InputStreamReader(fis2));
    StringBuilder sb1 = new StringBuilder();
    StringBuilder sb2 = new StringBuilder();
    String line1, line2;
    try 
        while ((line1 = input1.readLine()) != null && (line2 = input2.readLine()) != null) 
            sb1.append(line1);
            sb2.append(line2);
            if (!sb1.toString().equals(sb2.toString())) 
                return false;
            
        
     catch(Exception e) 
        e.printStackTrace();
     finally 
        if (fis1 != null) 
            fis1.close();
        
        if (fis2 != null) 
            fis2.close();
        
    
    return true;

我的问题是:

    我上面的代码有问题吗? 还有其他更有效的方法来解决这个问题吗?

【问题讨论】:

你应该关闭文件流 听起来像是codereview.stackexchange.com的问题 【参考方案1】:
    checkSame 不使用 hashing 通过比较 c1 != c2,您可以比较每次迭代读取的字节数 - 这是没有意义的 checkSame 仅将列表中的第一个文件与所有其他文件进行比较,但不会将秒数与所有其他文件和第三个文件进行比较,等等。

我会稍微修改一下您的方法:我将通过两个参数进行比较:哈希和文件大小。我将创建一个名为MyFile 的新类,它将包含三个字段:字符串名称、字符串哈希码、长尺寸。

然后我会迭代文件并使用您在hashing 中介绍的逻辑来创建MyFile 对象列表。大小可以轻松实现:

private static long fileSize(String filename) 
    File file = new File(filename);
    return file.length();

现在让MyFile 覆盖两个方法:hashCode()(它将根据您在上一步中计算的哈希码返回一个整数)和equals,它将仅检查哈希码和大小。例如:

class MyFile 

    String name;
    String hash;
    long size;

    public MyFile(String name, String hash, long size) 
        this.name = name;
        this.hash = hash;
        this.size = size;
    

    @Override
    public boolean equals(Object other) 
        if (!(other instanceof MyFile)) 
            return false;
        
        MyFile o = (MyFile)other;
        return o.hash.equals(this.hash) && o.size == this.size;
    

    @Override
    public int hashCode()
        return hash.hashCode();
    

现在您可以轻松比较文件并查看它们是否具有相似的内容(即使它们具有不同的路径)。

【讨论】:

感谢您的回答!我已按照您的建议重写了我的代码。但是我仍然对“如果两个文件具有相同的哈希值-其中一个文件将覆盖另一个文件(您最终在地图中只有一个文件)感到困惑。”,这是什么意思?你能解释一下吗?谢谢! checkSame() 仅将列表中的第一个文件与所有其他文件进行比较,但不会将秒数与所有其他文件和第三个文件进行比较,等等。---正如你所说,碰撞是长镜头,所以我只需要删除与他人不一样的文件。我的想法是将第一个文件作为基准,如果其余文件与第一个文件不同,则将其余文件添加到新列表中,然后递归解决。这个方法有问题吗? 关于 MAP:地图中不能有两个不同的项目具有相同的键,所以如果“file1”和“file2”都具有相同的哈希码:“key”你将添加第一个,您的 MAP 将如下所示:"key": "file1",当您添加第二个时,它将覆盖第一个,您最终会得到:"key": "file2" @YuqiShi 至于你的其他问题 - 这种方法很好,只是你可以通过使用 java8 流和groupby 更轻松地做到这一点 - 查一下! 但是 Map>,它的值是一个列表,这意味着当我添加第二个文件时,它应该是 ""key", file1, file2",这个“覆盖”的事情是如何发生的?@alfasin

以上是关于如何有效地输出具有相同内容的文件列表?的主要内容,如果未能解决你的问题,请参考以下文章

给定一个包含几千个文件的目录,请输出目录中所有文件名完全相同的列表[重复]

如何有效地将具有一定周期性的列表拆分为多个列表?

MonoTouch - 如何有效地创建具有所有 CGColor 类型的列表

在列表中有效地重复data.table,从循环中的另一个data.table顺序替换具有相同名称的列

如何有效地将大型 .tsv 文件上传到 pyspark 中具有拆分列的 Hive 表?

如何有效地漂亮打印 JSON 对象列表? [复制]