【发布时间】:2011-10-29 02:26:21
【问题描述】:
编写一个简单的程序,可以在我的计算机上找到完全重复的文件,但它有点慢。有什么办法可以加快速度吗?
using System;
using System.Collections.Generic;
using System.IO;
using System.Linq;
using System.Text;
using System.Security.Cryptography;
namespace DupeFinder
{
class Program
{
static void Main(string[] args)
{
Console.WriteLine("Getting files...");
var files = Directory.GetFiles(@"D:\Photos", "*", SearchOption.AllDirectories);
var alg = new HMACMD5();
var dict = new Dictionary<string,List<string>>();
Console.WriteLine("Computing hashes...");
int i=0;
int cursorLeft = Console.CursorLeft;
int cursorTop = Console.CursorTop;
foreach(var fileName in files)
{
Console.SetCursorPosition(cursorLeft,cursorTop);
Console.Write("Hashing file {0}/{1}", ++i, files.Length);
using(var stream = new BufferedStream(File.OpenRead(fileName),1024*1024*5))
{
var hash = alg.ComputeHash(stream);
var str = BitConverter.ToString(hash);
if (!dict.ContainsKey(str)) dict[str] = new List<string>();
dict[str].Add(fileName);
}
}
Console.WriteLine();
foreach(var dupe in dict.Where(p => p.Value.Count >= 2))
{
Console.WriteLine(string.Join(", ", dupe.Value));
}
Console.WriteLine("Done!");
Console.ReadLine();
}
}
}
可能的优化:
- 避免先将字节数组转换为字符串。我试过了,但它不起作用,我猜是因为它使用引用等于而不是比较字节。
- 更快的哈希算法?
- 不同的流或缓冲区大小?我试着做 1024^3 应该是一个兆字节,但这似乎会减慢它的速度。
或者这只是一件天生缓慢的事情?
我记得字典可以接受IEqualityComparer,所以我可以编写自己的byte[] 比较器。
我在互联网上找到的很多算法都倾向于先比较字节长度,我不需要这样做,因为我知道它总是 16 字节。他们也倾向于一次比较 1 个字节......但我在 64 位机器上,那为什么不做 8 个呢?
using System;
using System.Collections.Generic;
using System.IO;
using System.Linq;
using System.Text;
using System.Security.Cryptography;
namespace DupeFinder
{
class Program
{
static void Main(string[] args)
{
Console.WriteLine("Getting files...");
string dir = @"D:\Photos";
var files = Directory.GetFiles(dir, "*", SearchOption.AllDirectories);
var alg = new HMACMD5();
var dict = new Dictionary<byte[], List<string>>(new Md5Comparer());
Console.WriteLine("Computing hashes...");
int i = 0;
int cursorLeft = Console.CursorLeft;
int cursorTop = Console.CursorTop;
foreach (var fileName in files)
{
Console.SetCursorPosition(cursorLeft, cursorTop);
Console.Write("Hashing file {0}/{1}", ++i, files.Length);
using (var stream = new BufferedStream(File.OpenRead(fileName), 1024 * 1024 * 5))
{
var hash = alg.ComputeHash(stream);
if (!dict.ContainsKey(hash)) dict[hash] = new List<string>();
dict[hash].Add(fileName);
}
}
Console.WriteLine();
using (var sw = new StreamWriter(Path.Combine(dir, "duplicates.txt")))
{
i = 0;
foreach (var dupe in dict.Where(p => p.Value.Count >= 2))
{
sw.WriteLine("Duplicate {0}", ++i);
foreach(var fn in dupe.Value)
{
sw.WriteLine("- {0}", fn);
}
}
}
Console.WriteLine("Done!");
//Console.ReadLine();
}
}
class Md5Comparer : IEqualityComparer<byte[]>
{
public bool Equals(byte[] x, byte[] y)
{
var xi = BitConverter.ToInt64(x, 0);
var yi = BitConverter.ToInt64(y, 0);
if (xi != yi) return false;
xi = BitConverter.ToInt64(x, 8);
yi = BitConverter.ToInt64(y, 8);
return xi == yi;
}
public int GetHashCode(byte[] obj)
{
return obj[0];
}
}
}
不知道这有多快......我没有做过任何基准测试,但它肯定看起来并没有变慢。
新代码,感谢@spender:
using System;
using System.Collections.Generic;
using System.Diagnostics;
using System.IO;
using System.Linq;
using System.Text;
using System.Security.Cryptography;
namespace DupeFinder
{
class Program
{
static void Main(string[] args)
{
var watch = Stopwatch.StartNew();
const string dir = @"D:\Photos";
var md5Comparer = new Md5Comparer();
var dupeGroups = Directory.EnumerateFiles(dir, "*", SearchOption.AllDirectories)
.Select(fn => new FileInfo(fn))
.GroupBy(fi => fi.Length)
.Where(g => g.Count() > 1)
.SelectMany(g => g
.GroupBy(fi => GetHash(fi.FullName), md5Comparer)
.Where(g2 => g2.Count() > 1));
using (var sw = new StreamWriter(Path.Combine(dir, "duplicates.txt")))
{
int i = 0;
foreach (var dupeGroup in dupeGroups)
{
sw.WriteLine("Duplicate {0}", ++i);
foreach(FileInfo fi in dupeGroup)
{
sw.WriteLine("- {0}", fi.FullName);
}
}
}
Console.WriteLine("{0:0.000} seconds", watch.ElapsedMilliseconds / 1000d); // 22.068 seconds to process 10K files, 37 GB, 463 dupes
Console.ReadLine();
}
static readonly HMACMD5 md5Hasher = new HMACMD5();
public static byte[] GetHash(string fileName)
{
using(var stream = File.OpenRead(fileName))
return md5Hasher.ComputeHash(stream);
}
}
class Md5Comparer : IEqualityComparer<byte[]>
{
public bool Equals(byte[] x, byte[] y)
{
var xi = BitConverter.ToInt64(x, 0);
var yi = BitConverter.ToInt64(y, 0);
if (xi != yi) return false;
xi = BitConverter.ToInt64(x, 8);
yi = BitConverter.ToInt64(y, 8);
return xi == yi;
}
public int GetHashCode(byte[] obj)
{
return obj[0];
}
}
}
从 360+ 秒减少到 22-70 秒。进步很大!
【问题讨论】:
-
您是否在比较大量不断重新缓冲的大文件,使用 (var stream = new BufferedStream(File.OpenRead(fileName), 1024 * 1024 * 5)) 更改为 (var stream = File .OpenRead(fileName)) 在我的系统上执行得更快。
-
@Gary.S:是的...我只是尝试过。好像快了一点。我主要是散列照片。平均文件大小显然是 280 KiB。不用等……那是糟糕的数学。 3.64 MB。
-
您正在测试多少个文件以及需要多长时间。我让它在大约 4 秒内运行 9600 个文件。总大小约为 760 MB。
-
@Gary.S:10K 文件,37 GB。需要几分钟。
-
1024^3(如果意思是1024*1024*1024)是1Gb,不是1Mb
标签: c# optimization