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

时间:2016-08-07 10:07:50

标签: c# linq file directory duplicates

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

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

提前致谢,我可以随时提供所需的其他信息! :)

昭武

2 个答案:

答案 0 :(得分:6)

您忘记丢弃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)

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

答案 1 :(得分: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);