在 C# 中列出文件夹内的重复文件:利用 LINQ.AsParallel
Posted
技术标签:
【中文标题】在 C# 中列出文件夹内的重复文件:利用 LINQ.AsParallel【英文标题】:List duplicate files inside a folder in C#: Leveraging LINQ.AsParallel 【发布时间】:2012-09-27 15:29:33 【问题描述】:我在 C# 代码中编写了以下算法,以递归方式列出文件夹内的文件。
-
开始遍历目录及其子目录中的文件列表
目录。
将文件名和路径存储在列表中。
如果当前文件与列表中的任何其他文件匹配,则在
将两个文件标记为重复。
从列表中获取所有标记为重复的文件。
按名称分组并返回。
在包含 50,000 个文件和 12,000 个子目录的文件夹上执行非常慢。由于磁盘读取操作基本上是耗时的任务。甚至 LINQ.Parallel() 也无济于事。
实施:
class FileTuple public string FileName set; get; public string ContainingFolder set; get; public bool HasDuplicate set; get; public override bool Equals(object obj) if (this.FileName == (obj as FileTuple).FileName) return true; return false;
-
FileTuple 类跟踪文件名和包含目录,
标志跟踪重复状态。
我已经重写了 equals 方法来只比较文件名,在
fileTuples 的集合。
以下方法查找重复文件并作为列表返回。
private List<FileTuple> FindDuplicates()
List<FileTuple> fileTuples = new List<FileTuple>();
//Read all files from the given path
List<string> enumeratedFiles = Directory.EnumerateFiles(txtFolderPath.Text, "*.*", SearchOption.AllDirectories).Where(str => str.Contains(".exe") || str.Contains(".zip")).AsParallel().ToList();
foreach (string filePath in enumeratedFiles)
var name = Path.GetFileName(filePath);
var folder = Path.GetDirectoryName(filePath);
var currentFile = new FileTuple FileName = name, ContainingFolder = folder, HasDuplicate = false, ;
int foundIndex = fileTuples.IndexOf(currentFile);
//mark both files as duplicate, if found in list
//assuming only two duplicate file
if (foundIndex != -1)
currentFile.HasDuplicate = true;
fileTuples[foundIndex].HasDuplicate = true;
//keep of track of the file navigated
fileTuples.Add(currentFile);
List<FileTuple> duplicateFiles = fileTuples.Where(fileTuple => fileTuple.HasDuplicate).Select(fileTuple => fileTuple).OrderBy(fileTuple => fileTuple.FileName).AsParallel().ToList();
return duplicateFiles;
您能否提出一种提高性能的方法。
感谢您的帮助。
【问题讨论】:
一方面,您的代码无法生成任何匹配项,因为str.Contains("*.exe")
对于所有文件名都是错误的,因为星号。
第二,你处于一种命令式的心态。您正在将内容添加到列表等。状态突变不好,无论是顺序代码还是并发代码。相反,您应该对文件名进行分组并查找 Count() > 1
所在的组。
笔误,已更正,谢谢指出。
.Contains(".exe")
?来吧,你不应该使用.EndsWith(".exe")
吗?
@Theraot 甚至Path.GetExtension(filePath)=".exe"
。
【参考方案1】:
能否请您提出一种提高性能的方法。
一个明显的改进是使用Dictionary<FileTuple, FileTuple>
和List<FileTuple>
。这样你就不会在每次检查时都有 O(N) IndexOf
操作。请注意,您还需要覆盖 GetHashCode()
- 您应该已经收到关于此的警告。
我怀疑它会产生很大的不同 - 我希望这主要是 IO-bound。
此外,我怀疑最后的过滤和排序是否会成为一个重要的瓶颈,所以在最后一步使用AsParallel
不太可能做太多事情。当然,您应该衡量所有这些。
最后,整个方法可以变得相当简单,甚至不需要HasDuplicate
标志或Equals
/ GetHashCode
的任何覆盖:
private List<FileTuple> FindDuplicates()
return Directory.EnumerateFiles(txtFolderPath.Text, "*.*",
SearchOption.AllDirectories)
.Where(str => str.Contains(".exe") ||
str.Contains(".zip")
.Select(str => new FileTuple
FileName = Path.GetFileName(str),
ContainingFolder = Path.GetDirectoryName(str))
)
.GroupBy(tuple => tuple.FileName)
.Where(g => g.Count() > 1) // Only keep duplicates
.OrderBy(g => g.Key) // Order by filename
.SelectMany(g => g) // Flatten groups
.ToList();
【讨论】:
Linq 看起来很简单,你只需使用其中的一个 :) 开个玩笑,它看起来很高级 :) @radbyx:忽略它的整体“宏伟”——依次执行每一步。 LINQ 的美妙之处在于组合 - 您可以通过简单的步骤构建复杂的查询。 是的,当时采取每一步并理解它们并知道它们返回的内容必须是关键,就像你说的那样:) @JonSkeet 谢谢乔恩,假设要添加另一个过滤条件文件大小,我将在元组中添加一个新属性 FileSize。在这种情况下,我是否必须重复 .GroupBy(fileTuple => fileTuple.FileSize) .Where(g => g.Count() > 1) .OrderBy(g => g.Key) .SelectMany(g=>g ) 在扁平组之后? @autrevo:这意味着要成为分组的一部分吗?如果是这样,那么不 - 您将使用.GroupBy(tuple => new tuple.FileName, tuple.FileSize )
然后使用 .OrderBy(g => g.Key.FileName)
进行订购。基本上,您的评论并不完全清楚您的要求。【参考方案2】:
如果性能很关键,我可以建议使用来自 http://www.voidtools.com/download.php 的第三方库,尝试下载此工具并运行一些查询,它会很快点亮,它通过在整个文件系统上构建文件和目录的索引来工作在第一次运行时,索引在不到一分钟的时间内构建得非常快,并且在内存和磁盘上都需要一些时间,但之后查询会非常快,您可以在他们的 C# 示例中查看如何在代码中使用它。
【讨论】:
嗨迈克尔,代码看起来很有希望。您是否能够步入 Everything.c 的代码?我认为将代码包装到 C++/CLI 项目中,然后直接引用生成的 DLL 会更好。你说什么? 我没有尝试编译everything.c,我认为可以随心所欲,但我个人觉得使用现有的dll更安全。以上是关于在 C# 中列出文件夹内的重复文件:利用 LINQ.AsParallel的主要内容,如果未能解决你的问题,请参考以下文章