使用LINQ查找目录中的重复文件

本文关键字:文件 LINQ 查找 使用 | 更新日期: 2023-09-27 18:01:15

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

我的问题是我不希望重复发生。我应该指出,我正在处理一次最多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用删除功能替换打印功能,没有错误,但不起作用。

提前感谢,我随时可以获得所需的任何其他信息!:(

Akitake

使用LINQ查找目录中的重复文件

您忘记了处理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))
        });
    }
})

请勿使用Encoding.UTF8对任意字节(哈希就是这样(进行编码,因为结果可能是无效的UTF8序列。如果必须使用BitConverter.ToString,或者更好:找到一种不涉及字符串的不同方法。

例如,你可以写:

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

不过,您可以使用更好的方法:您可以先按大小对文件进行分组,如果有多个大小相同的文件,则仅计算哈希。当没有太多重复时,它应该会表现得更好。

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

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