【问题标题】:Find duplicate files in a directory using LINQ使用 LINQ 在目录中查找重复文件
【发布时间】:2016-12-13 06:35:53
【问题描述】:

我目前正在编写一个程序,该程序可以使用用户给定的参数从各种来源大量下载图像。

我的问题是我不希望发生重复。 我应该指出,我正在处理一次最多 100 个的大量下载(不是那么大),并且每个文件都有不同的名称,所以简单地按文件名搜索是行不通的,我需要检查哈希值。

无论如何,这是我已经找到的:

Directory.GetFiles(FullPath)
    .Select(f => new
        {
            FileName = f,
            FileHash = Encoding.UTF8.GetString(new SHA1Managed().ComputeHash(new FileStream(f, FileMode.Open, FileAccess.Read)))
        })
    .GroupBy(f => f.FileHash)
    .Select(g => new { FileHash = g.Key, Files = g.Select(z => z.FileName).ToList() })
    .SelectMany(f => f.Files.Skip(1))
    .ToList()
    .ForEach(File.Delete);

我的问题是,在“File.Delete”行,我得到了一个非常著名的错误,即该文件已被另一个进程使用。我认为这是因为上面的代码缺少在删除文件之前关闭它用来获取 FileHash 的 FileStream 的方法,但我不知道如何解决这个问题,有什么想法吗?

我还应该指出我已经尝试过其他解决方案,比如这个(没有 linq):https://www.bhalash.com/archives/13544802709 用删除的替换打印功能,没有错误但不起作用。

提前致谢,我随时为您提供所需的任何其他信息! :)

秋竹

【问题讨论】:

  • 啊,一个班轮的奇迹就可以做到这一切......
  • 对于任何偶然发现这个老问题的人,除了下面的优秀答案之外,我想补充一点,上面的代码有一些不必要的低效率。 ToList 调用会增加很多不必要的开销,FileHash 的第二个定义是完全没有必要的,因为它以后再也不会使用了。因此GroupBy 之后的行可以用更简单的.Select(g => g.Select(z => z.FileName)).SelectMany(f => f.Skip(1)) 替换,第二个ToList 调用可以通过将整个语句放在foreach 循环中并在foreach 主体内调用File.Delete 来消除。

标签: c# linq file directory duplicates


【解决方案1】:

你忘了释放FileStream,所以文件在GC收集到对象之前仍然是打开的。

您可以将Select 子句替换为:

.Select(f => {
    using (var fs = new FileStream(f, FileMode.Open, FileAccess.Read))
    {
        return new
        {
            FileName = f,
            FileHash = BitConverter.ToString(SHA1.Create().ComputeHash(fs))
        });
    }
})

NOT 使用Encoding.UTF8 编码任意字节(哈希是),因为结果可能是无效的UTF8 序列。如果必须使用BitConverter.ToString,或者更好的是:找到不涉及字符串的不同方式。

例如,你可以这样写:

.Select(f => {
    // Same as above, but with:
    // FileHash = SHA1.Create().ComputeHash(fs)
})
.GroupBy(f => f.FileHash, StructuralComparisons.StructuralEqualityComparer)

不过,您可以使用更好的方法:您可以先按大小对文件进行分组,然后在多个文件大小相同的情况下计算哈希值。当重复项不多时,这应该会表现得更好。

【讨论】:

  • 问题是:一旦遇到错误,例如其他进程正在使用任何文件,它将立即退出。
  • @CarneyCode 确实,您必须决定要做什么。我猜让抛出异常这里正确的默认方法,但如果你只想忽略这个错误,请将代码放在 lambda 中的 try { ... } catch (IOException) { ... } 块中。
  • StructuralComparisons.StructuralEqualityComparer 不适用于 .NetCore
【解决方案2】:

为了解决干净地处理文件流的问题,您可以将文件哈希的计算拆分为这样的方法:

static string GetHash(string path)
{
    using (var fileStream = new FileStream(path, FileMode.Open, FileAccess.Read))
    {
        return Encoding.UTF8.GetString(new SHA1Managed().ComputeHash(fileStream));
    }
}

并像这样消费它:

Directory.GetFiles(FullPath)
.Select(
f => new
{
    FileName = f,
    FileHash = GetHash(f)
})
.GroupBy(f => f.FileHash)
.Select(g => new { FileHash = g.Key, Files = g.Select(z => z.FileName).ToList() })
.SelectMany(f => f.Files.Skip(1))
.ToList()
.ForEach(File.Delete);

【讨论】:

  • 您的解决方案很可能会奏效,我以后会记住该解决方案,可能会有用。虽然@lucas-trzesniewski 解决方案对我来说看起来更干净:)
  • 是的,干净的代码是非常主观的。就个人而言,我喜欢从庞大的 LINQ 语句中删除逻辑,以减小大小并使其更具表现力。
  • 问题是:一旦遇到错误,例如其他进程正在使用任何文件,它将立即退出。
猜你喜欢
  • 2018-05-14
  • 2016-10-17
  • 2011-07-05
  • 1970-01-01
  • 1970-01-01
  • 1970-01-01
相关资源
最近更新 更多