C# 使用LINQ查找目录中的重复文件
我目前正在编写一个程序,可以从不同来源大量下载用户给定参数的图像 我的问题是我不想重复发生。 我应该指出,我正在处理一次最多100次的大规模下载(不是那么大规模),而且每个文件都有不同的名称,所以简单地按文件名搜索是不可行的,我需要检查哈希 不管怎么说,我已经发现了:C# 使用LINQ查找目录中的重复文件,c#,linq,file,directory,duplicates,C#,Linq,File,Directory,Duplicates,我目前正在编写一个程序,可以从不同来源大量下载用户给定参数的图像 我的问题是我不想重复发生。 我应该指出,我正在处理一次最多100次的大规模下载(不是那么大规模),而且每个文件都有不同的名称,所以简单地按文件名搜索是不可行的,我需要检查哈希 不管怎么说,我已经发现了: Directory.GetFiles(FullPath) .Select(f => new { FileName = f, FileHash = Enc
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”行中,我得到了一个非常著名的错误,该文件已经被另一个进程使用。我认为这是因为上面的代码在删除文件之前没有关闭文件流以获取文件哈希的方法,但我不知道如何解决这个问题,有什么想法吗
我还应该指出,我也尝试过其他解决方案,比如这个(没有linq):
将打印功能替换为删除功能,没有错误但不起作用
提前感谢,我随时准备提供所需的任何其他信息!:)
Akitake您忘记处理
文件流
,因此在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);
然后像这样消费:
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);
啊,一行代码的奇迹可以做到这一切……对于任何一个偶然发现这个老问题的人来说,除了下面的优秀答案之外,我想补充一点,上面的代码有一些不必要的低效。
ToList
调用会增加很多不必要的开销,而FileHash
的第二个定义完全不必要,因为以后再也不用了。因此,GroupBy
后面的行可以替换为更简单的.Select(g=>g.Select(z=>z.FileName)).SelectMany(f=>f.Skip(1))
,第二个ToList
调用可以通过将整个语句放在foreach循环中并在foreach主体内调用File.Delete来消除。您的解决方案很可能有效,我将在将来记住该解决方案,可能会很有用。虽然@lucas trzesniewski解决方案在我看来更干净:)是的,干净的代码非常主观。就我个人而言,我喜欢删除大型LINQ语句中的逻辑,以减小其大小并使其更具表现力。问题是:一旦遇到错误(如任何文件正在被另一个进程使用),它就会退出。问题是:一旦遇到错误(如任何文件正在被另一个进程使用),它就会退出。@CarneyCode确实,你必须决定做什么。我猜抛出异常是正确的默认方法,但是如果您只是想忽略此错误,请将代码放在lambda中的try{…}catch(IOException){…}
block.structuralComparations.StructuralEqualityComparer在.NetCore上不起作用