系列文章
1.分布式文件快速搜索V7.0(多计算机并行/多种算法)
2.分布式文件快速搜索的设计与实现(开源/分布式计算/并行)
前言
这个话题很古老了,用C#实现的也很多,很明显我是在造轮子了。不过我今晚闲得头疼,在codeplex碰见看见一个项目,就是找重复文件的,项目代码我没看,我只是想找事情做。对开发人员来说,舒展一下手指的最佳办法是敲代码了。我最喜欢通过写代码来学东西了,看着枯燥的书本就没劲。
需求
找重复文件,首先是要获取文件的特征,很久以前用CRC,虽然最近MD5已经给中国某大学研究员验证不可靠,不过我还是喜欢MD5:)。你可以选择SHA1,换个类名就可以了。
实现
实现的办法很简单,就是把所有文件的特征都算出来,然后比较。
问题是,如果盲目地对每个文件都先获取哈希特征,再进行比较,那会很浪费时间,譬如你的磁盘没有重复文件,或者有些很巨大的不重复文件,那你就要把绝大部分时间浪费在无谓的哈希获取上了。
所以,我们先使用文件大小进行碰撞,再对相同大小的进行碰撞,可以极大地提高效率。为什么选择文件大小呢?因为如果文件大小不一样,那就不可能内容重复了吧?为什么不同时选择文件的修改时间?文件修改时间不一样,文件内容也是可以一样的,对不?既然我们的目的只是找内容相同的文件(不是文件名相同),那我们就可以无视文件名和文件修改时间了。允许选择不同的特征组合,譬如先是单纯的文件大小,或者文件大小+文件名+创建时间。
实现过程是用Dictionary来根据特征分组,碰撞(相同文件内容)就放到该特征下的重复文件列表,最后找出所有重复的文件。
程序很简单,主要是原始文件和重复文件的选择。函数可以指定重复目录,如果匹配到是在重复目录下的,就认为是可删除的(相对保留原始文件而言,本程序没有删除代码。。。)重复的文件,如果没有指定重复目录,则自动选择第一个文件为原始文件。
并行的实现
一直想用多线程来实现提速,今晚闲得头疼,就简陋地实现。大概实现步骤:因为不是.NET3.5/4,所以没有PPL,只能模拟并行了:C# Parallelism: Executing Methods in Parallel in .NET 3.5 。原理是封装了ThreadPool,结合ManualResetEvent和WaitHandle.WaitAll等待所有线程完成。在.NET 2.0中,Dictionary不是线程安全的,又不想花时间自己写封装,就只有在网上找了个封装好的线程安全的Dictionary。多线程的思想是:分配任务,分而治之。业务逻辑是自行切割要检索的文件夹到不同的线程,得到结果后,最终实现并行计算。
网上发现的最quick & dirty的代码
http://www.hosca.com/blog/post/2010/04/13/using-LINQ-to-detect-and-remove-duplicate-files.aspx
这个代码极大地让感到我今晚写的代码很冗余。。。它用了几个Linq的语句:Select所有文件,GroupBy哈希值,然后ForEach删除。。。Linq是很精妙。。。不过这个办法跟我的老慢速算法是大同小异的,所以速度也是可以无视的。
我实现的代码(让你容易找点......)
老规矩,vs2005+c#2.0,quick & dirty,没linq,没var,没有自动属性。不要跟代码规范较真,也不要纠缠为什么代码都放一个文件,纯粹是为了方便粘贴到博客而已,在产品开发中,6000多个手工写的类我都是很有组织地归类的:)
代码中有7个算法:
WorkV1:最原始的,对每个文件都进行哈希值比对;
WorkV2:较新的算法,先用文件大小(或加上文件名、创建日期、修改日期等)进行过滤,匹配的文件组合才进行哈希值比对;
WorkV3:新的多线程算法,把多线程应用到文件检索和文件哈希值获取;
WorkV4:测试发现如果把多线程应用到文件读取,则性能急剧下降,但仅获取文件信息,则有很大提升,虽然瓶颈在文件读取,但起码在检索信息上,比起V2有了提升;
WorkV5:新的算法,文件检索使用多线程,文件哈希值获取则动态判断文件所在地物理磁盘,如果不是同一个物理磁盘,则是使用多线程,否则采用跟V2一样的算法;因为判断物理磁盘引入了System.Management.dll,使用了DriveIdentification条件编译;
WorkV6:新的算法,支持对互联网上的计算机进行搜索,哈希值的快速存取,文件内容搜索,全文索引;
WorkV7:最新的算法,添加了对协议提供者模式的支持,可以自由添加各种访问协议;
在这里比较性能其实意义不大,以为不同机器比较不同的文件,差异太大了,譬如你的机器有2个文件,一个巨大,一个渺小,用新的快速算法,微秒间完成,用老的慢算法,则你可能要去睡一觉。
程序有3个条件编译:DRIVEID启用物理磁盘识别(需要引用System.Management.dll),VERBOSE启用详细的调试信息,LoG启用保存日志
使用
看代码,判断支持多个文件夹。先Find出重复文件组合(指纹/相同文件列表),再用FindAll找出可以删除的所有重复文件。
如果要实现分布式搜索,必须在被搜索的计算机运行WorkV7Manager的实例,具体参看代码。
本地搜索
Dictionary<string, MatchFileItem> result;
BaseWork worker = new WorkV6();
result = worker.Find(new FileURI[] { new FileURI(@"c:\download\"), new FileURI(duplicateFolder)}
, new string[] { }, "", SearchTypes.Size | SearchTypes.Name, MatchTypes.ContentSame);
List<string> duplicatedFiles = worker.FindAll(result, duplicateFolder);
分布式搜索
初始化
WorkUtils.Initialiaze(new KeyValuePair<string, string>(), CompressionMethods.GZip, new System.Net.WebProxy());
服务器端
Dictionary<string, UserAccess> users = new Dictionary<string, UserAccess>();
users.Add("user", new UserAccess("user", "pass", UserRights.Discover | UserRights.Search));
string[] allowedPaths = new string[] { @"e:\temp\New Folder" };
manager.Start(users, allowedPaths, 8880);
客户端
Dictionary<string, MatchFileItem> result;
result = Worker.Find(new FileURI[] { new FileURI(@"c:\download\"), remoteFolder }
, new string[] { }, "YOUR KEYWORD HERE", SearchTypes.Size, MatchTypes.ContentExtract);
List<string> matchFiles = Worker.FindAll(result, string.Empty);
已知问题
本程序并没有考虑因为文件量巨大而会造成内存不足等问题,这个就留到以后我闲得更头痛的时候再考虑吧。
如果你要同时测试多种算法,那是不可能的,因为所有算法的瓶颈是对文件的哈希值获取,而这个方法允许Windows对已经访问的文件进行缓存和预取(pre-fetch),这样当你测试完第一个算法,再用第二个算法的时候,就会发现秒级完成。所以你每测试出一个结果,就应该重启电脑。。。
未知问题
如果你发现有什么bug,麻烦告诉我,我喜欢学习:)
改进
1.2010-7-16 v1;
2.2010-7-14 v2 添加了对选择文件名/修改时间/创建时间为重复标准的支持;重构代码,引入了基类,方便测试;
3.2010-7-14 v2.1 再次重构,并添加了对指定文件类型的支持,允许正则表达式;
4.2010-7-15 v3 增加了对多线程的支持,速度提升;
5.2010-7-16 v3.1 修正了从v2开始出现的重复测试问题;
6.2010-7-16 v4 仅应用多线程于文件查找;
7.2010-7-16 v5 动态支持多磁盘多线程提速;
8.2010-7-16 v5.1 增加了对使用文件属性过滤的支持,并重构了部分代码,并修正了动态多磁盘的判断;
9.2010-7-19 v5.2 允许单纯地查找匹配大小/文件名而不计算哈希值,增加了对文件名过滤的支持,并重构了部分代码,加入了对动态多磁盘的容错;
10.2010-7-20 v6 实现了网格搜索
10.2010-7-20 v6.1 添加了对文件发现的支持,如果远程计算机没有发布可访问的目录,则不进行搜索
10.2010-7-20 v6.2 添加了对删除重复文件的支持,并改造了用户访问机制
11.2010-7-21 v6.3 添加了对快速存取哈希值的支持;
12.2010-7-21 v6.4 修正了快速存取哈希值的异常问题;
13.2010-7-23 v6.6 添加了对HTTP协议、内容匹配、全文索引的支持;
14.2010-7-26 v6.7 添加了对HTTP、TCP压缩传输的支持;
15.2010-7-28 v6.8 添加了NTFS USN Journal技术,检索文件信息速度提升10倍,源代码:http://filio.codeplex.com/SourceControl/changeset/changes/74232
16.2010-7-31 v6.9 添加了对基于角色的访问控制(RBAC)的权限管理;
17.2010-8-11 v7.0 添加了对协议提供者模式的支持;
TODO
1.目前来看,改进速度的办法是多线程了,多个线程获取目录文件,然后多个线程对文件列表进行哈希获取。 在V3中添加了对多线程的支持;
2.添加对分布式(互联网上的机器)的支持;在v6中添加了对网格搜索的支持;
3.添加对删除文件的支持; 在v6.1中添加了对删除文件的支持;
4.文件内容哈希值存储,以便以后快速获取(同时记录文件名、大小、修改时间和哈希值,任一不匹配则认为文件改变了)在v6.3中实现了哈希值快速存取
5.添加对HTTP协议的支持;在v6.6中实现了对HTTP协议的支持
6.添加对搜索文件内容的支持:完全匹配、全文索引;在v6.6中添加了对文件内容匹配、全文索引的支持;
7.添加对lucene.net和hubbledotnet的支持;
8.添加对缓存的压缩支持;在V6.6添加了对缓存压缩的支持;
9.添加对网络传输(HTTP/TCP)的压缩支持;在v6.7添加了对网络压缩传输的支持;
10.添加对文件同步的支持:chunck/index/count,断点续传
11.添加对基于角色的访问控制(RBAC);在v6.9中添加了对其的支持;
12.如果检索互联网上的计算机,如果指定搜索目录存在大量的文件,则返回结果可能会因为太大而造成网络超时等问题;
13.添加对HTTP/TCP的SSL连接的支持;
14.添加对NTFS的USNJournal支持,可以比普通的检索文件信息(Directory.GetFiles)快10倍。。。 在v6.8中添加了对其的支持
代码下载
点击这里下载:Filio.zip
项目地址
本项目已经在http://filio.codeplex.com/ 开源