我目前正在编写一个程序,该程序使用来自用户的给定参数从各种来源大量下载图像。
我的问题是我不希望重复发生。 我应该指出,我一次处理大量下载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 用删除文件替换打印功能,没有错误但不起作用。
提前致谢,我可以随时提供所需的其他信息! :)
昭武
答案 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);