我想比较两个二进制文件。其中一个已经存储在服务器上,并在最初存储它时,将其CRC32预先计算到数据库中。
我知道如果CRC不同,则文件肯定不同。但是,如果CRC相同,则不能确定文件是否相同。因此,我正在寻找一种好的高效方法来比较这两个流:一个来自上传的文件,另一个来自文件系统。
我不是流方面的专家,但我很清楚在内存使用方面可能会出现问题。
static bool FileEquals(string fileName1, string fileName2)
{
// Check the file size and CRC equality here.. if they are equal...
using (var file1 = new FileStream(fileName1, FileMode.Open))
using (var file2 = new FileStream(fileName2, FileMode.Open))
return FileStreamEquals(file1, file2);
}
static bool FileStreamEquals(Stream stream1, Stream stream2)
{
const int bufferSize = 2048;
byte[] buffer1 = new byte[bufferSize]; //buffer size
byte[] buffer2 = new byte[bufferSize];
while (true) {
int count1 = stream1.Read(buffer1, 0, bufferSize);
int count2 = stream2.Read(buffer2, 0, bufferSize);
if (count1 != count2)
return false;
if (count1 == 0)
return true;
// You might replace the following with an efficient "memcmp"
if (!buffer1.Take(count1).SequenceEqual(buffer2.Take(count2)))
return false;
}
}
我通过在读取流块上循环使用Int64进行比较,加速了"memcmp"的执行速度。这将时间缩短到大约1/4。
private static bool StreamsContentsAreEqual(Stream stream1, Stream stream2)
{
const int bufferSize = 2048 * 2;
var buffer1 = new byte[bufferSize];
var buffer2 = new byte[bufferSize];
while (true)
{
int count1 = stream1.Read(buffer1, 0, bufferSize);
int count2 = stream2.Read(buffer2, 0, bufferSize);
if (count1 != count2)
{
return false;
}
if (count1 == 0)
{
return true;
}
int iterations = (int)Math.Ceiling((double)count1 / sizeof(Int64));
for (int i = 0; i < iterations; i++)
{
if (BitConverter.ToInt64(buffer1, i * sizeof(Int64)) != BitConverter.ToInt64(buffer2, i * sizeof(Int64)))
{
return false;
}
}
}
}
fileSize % sizeof(Int64) > 0
并且只有最后一个字节不同的情况吗? - Dan Bechard如果您不想依赖crc,我会这样做:
/// <summary>
/// Binary comparison of two files
/// </summary>
/// <param name="fileName1">the file to compare</param>
/// <param name="fileName2">the other file to compare</param>
/// <returns>a value indicateing weather the file are identical</returns>
public static bool CompareFiles(string fileName1, string fileName2)
{
FileInfo info1 = new FileInfo(fileName1);
FileInfo info2 = new FileInfo(fileName2);
bool same = info1.Length == info2.Length;
if (same)
{
using (FileStream fs1 = info1.OpenRead())
using (FileStream fs2 = info2.OpenRead())
using (BufferedStream bs1 = new BufferedStream(fs1))
using (BufferedStream bs2 = new BufferedStream(fs2))
{
for (long i = 0; i < info1.Length; i++)
{
if (bs1.ReadByte() != bs2.ReadByte())
{
same = false;
break;
}
}
}
}
return same;
}
被接受的答案存在一个错误,但从未进行更正:流读取调用不能保证返回所有请求的字节。
BinaryReader ReadBytes 调用保证返回所请求的字节数,除非先到达流的末尾。
以下代码利用BinaryReader进行比较:
static private bool FileEquals(string file1, string file2)
{
using (FileStream s1 = new FileStream(file1, FileMode.Open, FileAccess.Read, FileShare.Read))
using (FileStream s2 = new FileStream(file2, FileMode.Open, FileAccess.Read, FileShare.Read))
using (BinaryReader b1 = new BinaryReader(s1))
using (BinaryReader b2 = new BinaryReader(s2))
{
while (true)
{
byte[] data1 = b1.ReadBytes(64 * 1024);
byte[] data2 = b2.ReadBytes(64 * 1024);
if (data1.Length != data2.Length)
return false;
if (data1.Length == 0)
return true;
if (!data1.SequenceEqual(data2))
return false;
}
}
}
FileStream
上的Read
来获取丢失的字节。 - dafie您甚至可以在检查CRC之前检查两个文件的长度和日期,以可能避免进行CRC检查。
但是,如果您必须比较整个文件内容,一个好方法是按与CPU位数相等的步长读取字节。例如,在32位PC上,每次读取4个字节并将它们作为int32比较。在64位PC上,您可以一次读取8个字节。这大约比逐字节进行4或8倍快。您还可能需要使用unsafe代码块,以便您可以使用指针而不是进行大量位移和OR操作来将字节转换为本机int大小。
您可以使用IntPtr.Size来确定当前处理器架构的理想大小。
BinaryReader.ReadBytes
的源代码中添加了逻辑,以得到一个在每个循环中不重新创建缓冲区并且不受FileStream.Read
意外返回值影响的解决方案。public static bool AreSame(string path1, string path2) {
int BUFFER_SIZE = 64 * 1024;
byte[] buffer1 = new byte[BUFFER_SIZE];
byte[] buffer2 = new byte[BUFFER_SIZE];
int ReadBytes(FileStream fs, byte[] buffer) {
int totalBytes = 0;
int count = buffer.Length;
while (count > 0) {
int readBytes = fs.Read(buffer, totalBytes, count);
if (readBytes == 0)
break;
totalBytes += readBytes;
count -= readBytes;
}
return totalBytes;
}
using (FileStream fs1 = new FileStream(path1, FileMode.Open, FileAccess.Read, FileShare.Read))
using (FileStream fs2 = new FileStream(path2, FileMode.Open, FileAccess.Read, FileShare.Read)) {
while (true) {
int count1 = ReadBytes(fs1, buffer1);
int count2 = ReadBytes(fs2, buffer2);
if (count1 != count2)
return false;
if (count1 == 0)
return true;
if (count1 == BUFFER_SIZE) {
if (!buffer1.SequenceEqual(buffer2))
return false;
} else {
if (!buffer1.Take(count1).SequenceEqual(buffer2.Take(count2)))
return false;
}
}
}
}
FileStreamEquals
应该接受两个正确类型的FileStream
参数;弱情况下可以可能认为如果没有出现问题,从文件中请求 n 字节的Read
请求确实读取了 n 字节。但是你敢打赌你的生命(或你的公司)在每种情况下都能如此吗?网络映射驱动器呢?命名管道呢? - Peter - Reinstate Monica