Các cách tiếp cận thông thường khuyên bạn nên đọc tệp nhị phân qua FileStream và so sánh từng byte một.
- Một so sánh tổng kiểm tra như CRC sẽ nhanh hơn?
- Có thư viện .NET nào có thể tạo tổng kiểm tra cho một tệp không?
Các cách tiếp cận thông thường khuyên bạn nên đọc tệp nhị phân qua FileStream và so sánh từng byte một.
Câu trả lời:
So sánh tổng kiểm tra rất có thể sẽ chậm hơn so với so sánh theo byte.
Để tạo tổng kiểm tra, bạn sẽ cần tải từng byte của tệp và thực hiện xử lý trên đó. Sau đó, bạn sẽ phải làm điều này trên tập tin thứ hai. Việc xử lý gần như chắc chắn sẽ chậm hơn so với kiểm tra so sánh.
Đối với việc tạo tổng kiểm tra: Bạn có thể thực hiện việc này một cách dễ dàng với các lớp mật mã. Đây là một ví dụ ngắn về việc tạo tổng kiểm MD5 với C #.
Tuy nhiên, tổng kiểm tra có thể nhanh hơn và có ý nghĩa hơn nếu bạn có thể tính toán trước tổng kiểm tra của trường hợp "kiểm tra" hoặc "cơ sở". Nếu bạn có một tệp hiện có và bạn đang kiểm tra xem một tệp mới có giống với tệp hiện có hay không, thì việc tính toán trước tổng kiểm tra trên tệp "hiện tại" của bạn chỉ có nghĩa là chỉ cần thực hiện DiskIO một lần, trên tập tin mới. Điều này có thể sẽ nhanh hơn so với so sánh theo byte.
Phương pháp chậm nhất có thể là so sánh hai tệp byte theo byte. Cách nhanh nhất tôi có thể đưa ra là so sánh tương tự, nhưng thay vì một byte mỗi lần, bạn sẽ sử dụng một mảng byte có kích thước bằng Int64, sau đó so sánh các số kết quả.
Đây là những gì tôi nghĩ ra:
const int BYTES_TO_READ = sizeof(Int64);
static bool FilesAreEqual(FileInfo first, FileInfo second)
{
if (first.Length != second.Length)
return false;
if (string.Equals(first.FullName, second.FullName, StringComparison.OrdinalIgnoreCase))
return true;
int iterations = (int)Math.Ceiling((double)first.Length / BYTES_TO_READ);
using (FileStream fs1 = first.OpenRead())
using (FileStream fs2 = second.OpenRead())
{
byte[] one = new byte[BYTES_TO_READ];
byte[] two = new byte[BYTES_TO_READ];
for (int i = 0; i < iterations; i++)
{
fs1.Read(one, 0, BYTES_TO_READ);
fs2.Read(two, 0, BYTES_TO_READ);
if (BitConverter.ToInt64(one,0) != BitConverter.ToInt64(two,0))
return false;
}
}
return true;
}
Trong thử nghiệm của mình, tôi đã có thể thấy điều này vượt trội hơn so với kịch bản ReadByte () đơn giản gần 3: 1. Tính trung bình trên 1000 lần chạy, tôi có phương thức này ở tốc độ 1063ms và phương thức bên dưới (so sánh đơn giản bằng cách so sánh byte) ở 3031ms. Băm luôn trở lại phụ thứ hai ở mức trung bình 865ms. Thử nghiệm này là với một tệp video ~ 100 MB.
Đây là phương pháp ReadByte và băm tôi đã sử dụng, để so sánh:
static bool FilesAreEqual_OneByte(FileInfo first, FileInfo second)
{
if (first.Length != second.Length)
return false;
if (string.Equals(first.FullName, second.FullName, StringComparison.OrdinalIgnoreCase))
return true;
using (FileStream fs1 = first.OpenRead())
using (FileStream fs2 = second.OpenRead())
{
for (int i = 0; i < first.Length; i++)
{
if (fs1.ReadByte() != fs2.ReadByte())
return false;
}
}
return true;
}
static bool FilesAreEqual_Hash(FileInfo first, FileInfo second)
{
byte[] firstHash = MD5.Create().ComputeHash(first.OpenRead());
byte[] secondHash = MD5.Create().ComputeHash(second.OpenRead());
for (int i=0; i<firstHash.Length; i++)
{
if (firstHash[i] != secondHash[i])
return false;
}
return true;
}
FilesAreEqual_Hash
phương pháp nên có một using
trên cả hai tập tin suối quá như ReadByte
phương pháp nếu không nó sẽ treo trên cho cả hai tập tin.
FileStream.Read()
thực sự có thể đọc ít byte hơn số lượng được yêu cầu. Bạn nên sử dụng StreamReader.ReadBlock()
thay thế.
Nếu bạn làm quyết định bạn thực sự cần một byte-by-byte so sánh đầy đủ (xem câu trả lời khác để thảo luận các băm), sau đó là giải pháp đơn giản nhất là:
• System.IO.FileInfo
ví dụ:
public static bool AreFileContentsEqual(FileInfo fi1, FileInfo fi2) =>
fi1.Length == fi2.Length &&
(fi1.Length == 0 || File.ReadAllBytes(fi1.FullName).SequenceEqual(
File.ReadAllBytes(fi2.FullName)));
• cho System.String
tên đường dẫn:
public static bool AreFileContentsEqual(String path1, String path2) =>
AreFileContentsEqual(new FileInfo(path1), new FileInfo(path2));
Không giống như một số câu trả lời được đăng khác, điều này hoàn toàn chính xác cho bất kỳ loại tệp nào: nhị phân, văn bản, phương tiện, thực thi, v.v., nhưng như một so sánh nhị phân đầy đủ , các tệp chỉ khác nhau theo cách "không quan trọng" (như BOM , dòng -ending , mã hóa ký tự , siêu dữ liệu phương tiện, khoảng trắng, phần đệm, nhận xét mã nguồn, v.v.) sẽ luôn được coi là không bằng nhau .
Mã này tải cả hai tệp vào bộ nhớ hoàn toàn, vì vậy không nên sử dụng nó để so sánh các tệp thực sự khổng lồ . Ngoài sự cảnh báo quan trọng đó, tải đầy đủ thực sự không phải là một hình phạt đối với thiết kế của .NET GC (vì về cơ bản nó được tối ưu hóa để giữ các phân bổ nhỏ, ngắn hạn cực kỳ rẻ ), và trên thực tế thậm chí có thể tối ưu khi kích thước tệp được mong đợi được ít hơn 85k , vì sử dụng tối thiểu là mã người dùng (như trình bày ở đây) ngụ ý tối đa ủy thác vấn đề hiệu suất tập tin vào CLR
, BCL
và JIT
để được hưởng lợi từ (ví dụ) công nghệ mới nhất thiết kế, mã hệ thống, và tối ưu hóa thời gian chạy thích nghi.
Hơn nữa, đối với các kịch bản công việc như vậy, mối quan tâm về hiệu suất so sánh từng byte thông qua LINQ
các điều tra viên (như được hiển thị ở đây) là không cần thiết, vì việc nhấn vào đĩa a̲t̲ a̲l̲l̲ cho tệp I / O sẽ giảm đi, theo một số bậc độ lớn, lợi ích của các lựa chọn so sánh bộ nhớ khác nhau. Ví dụ, mặc dù SequenceEqual
không thực tế cho chúng ta những "tối ưu hóa" của bỏ trên không phù hợp đầu tiên , điều này hầu như không quan trọng sau khi đã lấy nội dung của các tác phẩm, mỗi hoàn toàn cần thiết để khẳng định trận đấu ..
Ngoài câu trả lời của Reed Copsey :
Trường hợp xấu nhất là hai tệp giống hệt nhau. Trong trường hợp này, tốt nhất là so sánh các tệp theo từng byte.
Nếu hai tệp không giống nhau, bạn có thể tăng tốc mọi thứ lên một chút bằng cách phát hiện sớm hơn rằng chúng không giống nhau.
Ví dụ: nếu hai tệp có độ dài khác nhau thì bạn biết chúng không thể giống nhau và thậm chí bạn không phải so sánh nội dung thực tế của chúng.
Nó thậm chí còn nhanh hơn nếu bạn không đọc trong các đoạn nhỏ 8 byte mà đặt một vòng lặp, đọc một đoạn lớn hơn. Tôi giảm thời gian so sánh trung bình xuống còn 1/4.
public static bool FilesContentsAreEqual(FileInfo fileInfo1, FileInfo fileInfo2)
{
bool result;
if (fileInfo1.Length != fileInfo2.Length)
{
result = false;
}
else
{
using (var file1 = fileInfo1.OpenRead())
{
using (var file2 = fileInfo2.OpenRead())
{
result = StreamsContentsAreEqual(file1, file2);
}
}
}
return result;
}
private static bool StreamsContentsAreEqual(Stream stream1, Stream stream2)
{
const int bufferSize = 1024 * sizeof(Int64);
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;
}
}
}
}
}
count1 != count2
không chính xác. Stream.Read()
có thể trả lại ít hơn số lượng bạn đã cung cấp, vì nhiều lý do.
Int64
khối chẵn , bạn có thể muốn tính kích thước như thế này : const int bufferSize = 1024 * sizeof(Int64)
.
Điều duy nhất có thể làm cho một so sánh tổng kiểm tra nhanh hơn một chút so với so sánh từng byte là thực tế là bạn đang đọc một tệp tại một thời điểm, phần nào giảm thời gian tìm kiếm cho đầu đĩa. Tuy nhiên, mức tăng nhẹ đó có thể bị ăn mòn bởi thời gian tính toán băm.
Ngoài ra, một so sánh tổng kiểm tra tất nhiên chỉ có bất kỳ cơ hội nhanh hơn nếu các tập tin là giống hệt nhau. Nếu chúng không như vậy, một so sánh từng byte sẽ kết thúc ở sự khác biệt đầu tiên, làm cho nó nhanh hơn rất nhiều.
Bạn cũng nên xem xét rằng so sánh mã băm chỉ cho bạn biết rằng rất có khả năng các tệp giống hệt nhau. Để chắc chắn 100%, bạn cần thực hiện so sánh theo từng byte.
Ví dụ, nếu mã băm là 32 bit, bạn chắc chắn khoảng 99.99999998% rằng các tệp giống hệt nhau nếu mã băm khớp. Đó là gần 100%, nhưng nếu bạn thực sự cần 100% chắc chắn, thì không phải vậy.
1 - (1 / (2^32))
, đó là xác suất mà bất kỳ tệp nào sẽ có một số băm 32 bit nhất định. Xác suất hai tệp khác nhau có cùng hàm băm là như nhau, vì tệp đầu tiên cung cấp giá trị băm "đã cho" và chúng ta chỉ cần xem xét liệu tệp kia có khớp với giá trị đó hay không. Cơ hội với băm 64 và 128 bit giảm xuống 99.999999999999999994% và 99.9999999999999999999999999999999999997% (tương ứng), như thể đó là vấn đề với những con số không thể tưởng tượng được.
Chỉnh sửa: Phương pháp này sẽ không hoạt động để so sánh các tệp nhị phân!
Trong .NET 4.0, File
lớp có hai phương thức mới sau đây:
public static IEnumerable<string> ReadLines(string path)
public static IEnumerable<string> ReadLines(string path, Encoding encoding)
Có nghĩa là bạn có thể sử dụng:
bool same = File.ReadLines(path1).SequenceEqual(File.ReadLines(path2));
Thành thật mà nói, tôi nghĩ bạn cần cắt tỉa cây tìm kiếm của bạn xuống càng nhiều càng tốt.
Những điều cần kiểm tra trước khi đi từng byte:
Ngoài ra, đọc các khối lớn tại một thời điểm sẽ hiệu quả hơn vì các ổ đĩa đọc các byte tuần tự nhanh hơn. Việc đi từng byte một không chỉ gây ra nhiều cuộc gọi hệ thống hơn mà còn khiến đầu đọc của ổ cứng truyền thống tìm kiếm qua lại thường xuyên hơn nếu cả hai tệp nằm trên cùng một ổ đĩa.
Đọc đoạn A và đoạn B vào bộ đệm byte và so sánh chúng (KHÔNG sử dụng Array.Equals, xem bình luận). Điều chỉnh kích thước của các khối cho đến khi bạn đạt được những gì bạn cảm thấy là một sự đánh đổi tốt giữa bộ nhớ và hiệu suất. Bạn cũng có thể đa luồng so sánh, nhưng không đa luồng mà đĩa đọc được.
Câu trả lời của tôi là một dẫn xuất của @lars nhưng sửa lỗi trong lệnh gọi đến Stream.Read
. Tôi cũng thêm một số kiểm tra đường dẫn nhanh mà các câu trả lời khác có, và xác nhận đầu vào. Nói tóm lại, điều này nên được những câu trả lời:
using System;
using System.IO;
namespace ConsoleApp4
{
class Program
{
static void Main(string[] args)
{
var fi1 = new FileInfo(args[0]);
var fi2 = new FileInfo(args[1]);
Console.WriteLine(FilesContentsAreEqual(fi1, fi2));
}
public static bool FilesContentsAreEqual(FileInfo fileInfo1, FileInfo fileInfo2)
{
if (fileInfo1 == null)
{
throw new ArgumentNullException(nameof(fileInfo1));
}
if (fileInfo2 == null)
{
throw new ArgumentNullException(nameof(fileInfo2));
}
if (string.Equals(fileInfo1.FullName, fileInfo2.FullName, StringComparison.OrdinalIgnoreCase))
{
return true;
}
if (fileInfo1.Length != fileInfo2.Length)
{
return false;
}
else
{
using (var file1 = fileInfo1.OpenRead())
{
using (var file2 = fileInfo2.OpenRead())
{
return StreamsContentsAreEqual(file1, file2);
}
}
}
}
private static int ReadFullBuffer(Stream stream, byte[] buffer)
{
int bytesRead = 0;
while (bytesRead < buffer.Length)
{
int read = stream.Read(buffer, bytesRead, buffer.Length - bytesRead);
if (read == 0)
{
// Reached end of stream.
return bytesRead;
}
bytesRead += read;
}
return bytesRead;
}
private static bool StreamsContentsAreEqual(Stream stream1, Stream stream2)
{
const int bufferSize = 1024 * sizeof(Int64);
var buffer1 = new byte[bufferSize];
var buffer2 = new byte[bufferSize];
while (true)
{
int count1 = ReadFullBuffer(stream1, buffer1);
int count2 = ReadFullBuffer(stream2, buffer2);
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;
}
}
}
}
}
}
Hoặc nếu bạn muốn trở nên siêu tuyệt vời, bạn có thể sử dụng biến thể async:
using System;
using System.IO;
using System.Threading.Tasks;
namespace ConsoleApp4
{
class Program
{
static void Main(string[] args)
{
var fi1 = new FileInfo(args[0]);
var fi2 = new FileInfo(args[1]);
Console.WriteLine(FilesContentsAreEqualAsync(fi1, fi2).GetAwaiter().GetResult());
}
public static async Task<bool> FilesContentsAreEqualAsync(FileInfo fileInfo1, FileInfo fileInfo2)
{
if (fileInfo1 == null)
{
throw new ArgumentNullException(nameof(fileInfo1));
}
if (fileInfo2 == null)
{
throw new ArgumentNullException(nameof(fileInfo2));
}
if (string.Equals(fileInfo1.FullName, fileInfo2.FullName, StringComparison.OrdinalIgnoreCase))
{
return true;
}
if (fileInfo1.Length != fileInfo2.Length)
{
return false;
}
else
{
using (var file1 = fileInfo1.OpenRead())
{
using (var file2 = fileInfo2.OpenRead())
{
return await StreamsContentsAreEqualAsync(file1, file2).ConfigureAwait(false);
}
}
}
}
private static async Task<int> ReadFullBufferAsync(Stream stream, byte[] buffer)
{
int bytesRead = 0;
while (bytesRead < buffer.Length)
{
int read = await stream.ReadAsync(buffer, bytesRead, buffer.Length - bytesRead).ConfigureAwait(false);
if (read == 0)
{
// Reached end of stream.
return bytesRead;
}
bytesRead += read;
}
return bytesRead;
}
private static async Task<bool> StreamsContentsAreEqualAsync(Stream stream1, Stream stream2)
{
const int bufferSize = 1024 * sizeof(Int64);
var buffer1 = new byte[bufferSize];
var buffer2 = new byte[bufferSize];
while (true)
{
int count1 = await ReadFullBufferAsync(stream1, buffer1).ConfigureAwait(false);
int count2 = await ReadFullBufferAsync(stream2, buffer2).ConfigureAwait(false);
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;
}
}
}
}
}
}
Các thử nghiệm của tôi cho thấy rằng nó chắc chắn giúp gọi Stream.ReadByte () ít lần hơn, nhưng sử dụng BitConverter để gói byte không tạo ra nhiều khác biệt so với việc so sánh các byte trong một mảng byte.
Vì vậy, có thể thay thế vòng lặp "Math.Cading và lặp lại" trong nhận xét ở trên bằng cách đơn giản nhất:
for (int i = 0; i < count1; i++)
{
if (buffer1[i] != buffer2[i])
return false;
}
Tôi đoán nó có liên quan đến thực tế là BitConverter.ToInt64 cần thực hiện một chút công việc (kiểm tra các đối số và sau đó thực hiện dịch chuyển bit) trước khi bạn so sánh và kết thúc là cùng một lượng công việc như so sánh 8 byte trong hai mảng .
Nếu các tệp không quá lớn, bạn có thể sử dụng:
public static byte[] ComputeFileHash(string fileName)
{
using (var stream = File.OpenRead(fileName))
return System.Security.Cryptography.MD5.Create().ComputeHash(stream);
}
Sẽ chỉ khả thi khi so sánh băm nếu băm là hữu ích để lưu trữ.
(Đã chỉnh sửa mã để một cái gì đó sạch sẽ hơn nhiều.)
Một cải tiến khác trên các tệp lớn có độ dài giống hệt nhau, có thể là không đọc các tệp một cách tuần tự, mà là so sánh các khối ngẫu nhiên nhiều hơn hoặc ít hơn.
Bạn có thể sử dụng nhiều luồng, bắt đầu trên các vị trí khác nhau trong tệp và so sánh tiến hoặc lùi.
Bằng cách này, bạn có thể phát hiện các thay đổi ở giữa / cuối tệp, nhanh hơn bạn sẽ đến đó bằng cách sử dụng cách tiếp cận tuần tự.
Nếu bạn chỉ cần so sánh hai tệp, tôi đoán cách nhanh nhất sẽ là (trong C, tôi không biết liệu nó có áp dụng được với .NET không)
OTOH, nếu bạn cần tìm xem có các tệp trùng lặp trong một tập hợp N tệp hay không, thì cách nhanh nhất chắc chắn là sử dụng hàm băm để tránh so sánh từng bit của N-way.
Một cái gì đó (hy vọng) hợp lý hiệu quả:
public class FileCompare
{
public static bool FilesEqual(string fileName1, string fileName2)
{
return FilesEqual(new FileInfo(fileName1), new FileInfo(fileName2));
}
/// <summary>
///
/// </summary>
/// <param name="file1"></param>
/// <param name="file2"></param>
/// <param name="bufferSize">8kb seemed like a good default</param>
/// <returns></returns>
public static bool FilesEqual(FileInfo file1, FileInfo file2, int bufferSize = 8192)
{
if (!file1.Exists || !file2.Exists || file1.Length != file2.Length) return false;
var buffer1 = new byte[bufferSize];
var buffer2 = new byte[bufferSize];
using (var stream1 = file1.Open(FileMode.Open, FileAccess.Read, FileShare.Read))
{
using (var stream2 = file2.Open(FileMode.Open, FileAccess.Read, FileShare.Read))
{
while (true)
{
var bytesRead1 = stream1.Read(buffer1, 0, bufferSize);
var bytesRead2 = stream2.Read(buffer2, 0, bufferSize);
if (bytesRead1 != bytesRead2) return false;
if (bytesRead1 == 0) return true;
if (!ArraysEqual(buffer1, buffer2, bytesRead1)) return false;
}
}
}
}
/// <summary>
///
/// </summary>
/// <param name="array1"></param>
/// <param name="array2"></param>
/// <param name="bytesToCompare"> 0 means compare entire arrays</param>
/// <returns></returns>
public static bool ArraysEqual(byte[] array1, byte[] array2, int bytesToCompare = 0)
{
if (array1.Length != array2.Length) return false;
var length = (bytesToCompare == 0) ? array1.Length : bytesToCompare;
var tailIdx = length - length % sizeof(Int64);
//check in 8 byte chunks
for (var i = 0; i < tailIdx; i += sizeof(Int64))
{
if (BitConverter.ToInt64(array1, i) != BitConverter.ToInt64(array2, i)) return false;
}
//check the remainder of the array, always shorter than 8 bytes
for (var i = tailIdx; i < length; i++)
{
if (array1[i] != array2[i]) return false;
}
return true;
}
}
Dưới đây là một số chức năng tiện ích cho phép bạn xác định xem hai tệp (hoặc hai luồng) có chứa dữ liệu giống nhau không.
Tôi đã cung cấp một phiên bản "nhanh" đa luồng vì nó so sánh các mảng byte (mỗi bộ đệm được điền từ những gì đã đọc trong mỗi tệp) trong các luồng khác nhau bằng cách sử dụng Tác vụ.
Như mong đợi, nó nhanh hơn nhiều (nhanh hơn khoảng 3 lần) nhưng nó tiêu tốn nhiều CPU hơn (vì nó đa luồng) và nhiều bộ nhớ hơn (vì nó cần hai bộ đệm mảng byte cho mỗi luồng so sánh).
public static bool AreFilesIdenticalFast(string path1, string path2)
{
return AreFilesIdentical(path1, path2, AreStreamsIdenticalFast);
}
public static bool AreFilesIdentical(string path1, string path2)
{
return AreFilesIdentical(path1, path2, AreStreamsIdentical);
}
public static bool AreFilesIdentical(string path1, string path2, Func<Stream, Stream, bool> areStreamsIdentical)
{
if (path1 == null)
throw new ArgumentNullException(nameof(path1));
if (path2 == null)
throw new ArgumentNullException(nameof(path2));
if (areStreamsIdentical == null)
throw new ArgumentNullException(nameof(path2));
if (!File.Exists(path1) || !File.Exists(path2))
return false;
using (var thisFile = new FileStream(path1, FileMode.Open, FileAccess.Read, FileShare.ReadWrite))
{
using (var valueFile = new FileStream(path2, FileMode.Open, FileAccess.Read, FileShare.ReadWrite))
{
if (valueFile.Length != thisFile.Length)
return false;
if (!areStreamsIdentical(thisFile, valueFile))
return false;
}
}
return true;
}
public static bool AreStreamsIdenticalFast(Stream stream1, Stream stream2)
{
if (stream1 == null)
throw new ArgumentNullException(nameof(stream1));
if (stream2 == null)
throw new ArgumentNullException(nameof(stream2));
const int bufsize = 80000; // 80000 is below LOH (85000)
var tasks = new List<Task<bool>>();
do
{
// consumes more memory (two buffers for each tasks)
var buffer1 = new byte[bufsize];
var buffer2 = new byte[bufsize];
int read1 = stream1.Read(buffer1, 0, buffer1.Length);
if (read1 == 0)
{
int read3 = stream2.Read(buffer2, 0, 1);
if (read3 != 0) // not eof
return false;
break;
}
// both stream read could return different counts
int read2 = 0;
do
{
int read3 = stream2.Read(buffer2, read2, read1 - read2);
if (read3 == 0)
return false;
read2 += read3;
}
while (read2 < read1);
// consumes more cpu
var task = Task.Run(() =>
{
return IsSame(buffer1, buffer2);
});
tasks.Add(task);
}
while (true);
Task.WaitAll(tasks.ToArray());
return !tasks.Any(t => !t.Result);
}
public static bool AreStreamsIdentical(Stream stream1, Stream stream2)
{
if (stream1 == null)
throw new ArgumentNullException(nameof(stream1));
if (stream2 == null)
throw new ArgumentNullException(nameof(stream2));
const int bufsize = 80000; // 80000 is below LOH (85000)
var buffer1 = new byte[bufsize];
var buffer2 = new byte[bufsize];
var tasks = new List<Task<bool>>();
do
{
int read1 = stream1.Read(buffer1, 0, buffer1.Length);
if (read1 == 0)
return stream2.Read(buffer2, 0, 1) == 0; // check not eof
// both stream read could return different counts
int read2 = 0;
do
{
int read3 = stream2.Read(buffer2, read2, read1 - read2);
if (read3 == 0)
return false;
read2 += read3;
}
while (read2 < read1);
if (!IsSame(buffer1, buffer2))
return false;
}
while (true);
}
public static bool IsSame(byte[] bytes1, byte[] bytes2)
{
if (bytes1 == null)
throw new ArgumentNullException(nameof(bytes1));
if (bytes2 == null)
throw new ArgumentNullException(nameof(bytes2));
if (bytes1.Length != bytes2.Length)
return false;
for (int i = 0; i < bytes1.Length; i++)
{
if (bytes1[i] != bytes2[i])
return false;
}
return true;
}
Tôi nghĩ có những ứng dụng mà "băm" nhanh hơn so với so sánh từng byte. Nếu bạn cần so sánh một tệp với người khác hoặc có hình thu nhỏ của ảnh có thể thay đổi. Nó phụ thuộc vào nơi và cách sử dụng.
private bool CompareFilesByte(string file1, string file2)
{
using (var fs1 = new FileStream(file1, FileMode.Open))
using (var fs2 = new FileStream(file2, FileMode.Open))
{
if (fs1.Length != fs2.Length) return false;
int b1, b2;
do
{
b1 = fs1.ReadByte();
b2 = fs2.ReadByte();
if (b1 != b2 || b1 < 0) return false;
}
while (b1 >= 0);
}
return true;
}
private string HashFile(string file)
{
using (var fs = new FileStream(file, FileMode.Open))
using (var reader = new BinaryReader(fs))
{
var hash = new SHA512CryptoServiceProvider();
hash.ComputeHash(reader.ReadBytes((int)file.Length));
return Convert.ToBase64String(hash.Hash);
}
}
private bool CompareFilesWithHash(string file1, string file2)
{
var str1 = HashFile(file1);
var str2 = HashFile(file2);
return str1 == str2;
}
Ở đây, bạn có thể nhận được những gì là nhanh nhất.
var sw = new Stopwatch();
sw.Start();
var compare1 = CompareFilesWithHash(receiveLogPath, logPath);
sw.Stop();
Debug.WriteLine(string.Format("Compare using Hash {0}", sw.ElapsedTicks));
sw.Reset();
sw.Start();
var compare2 = CompareFilesByte(receiveLogPath, logPath);
sw.Stop();
Debug.WriteLine(string.Format("Compare byte-byte {0}", sw.ElapsedTicks));
Tùy chọn, chúng ta có thể lưu băm trong cơ sở dữ liệu.
Hy vọng điều này có thể giúp
Một câu trả lời khác, bắt nguồn từ @chsh. MD5 với cách sử dụng và lối tắt cho cùng một tệp, tệp không tồn tại và độ dài khác nhau:
/// <summary>
/// Performs an md5 on the content of both files and returns true if
/// they match
/// </summary>
/// <param name="file1">first file</param>
/// <param name="file2">second file</param>
/// <returns>true if the contents of the two files is the same, false otherwise</returns>
public static bool IsSameContent(string file1, string file2)
{
if (file1 == file2)
return true;
FileInfo file1Info = new FileInfo(file1);
FileInfo file2Info = new FileInfo(file2);
if (!file1Info.Exists && !file2Info.Exists)
return true;
if (!file1Info.Exists && file2Info.Exists)
return false;
if (file1Info.Exists && !file2Info.Exists)
return false;
if (file1Info.Length != file2Info.Length)
return false;
using (FileStream file1Stream = file1Info.OpenRead())
using (FileStream file2Stream = file2Info.OpenRead())
{
byte[] firstHash = MD5.Create().ComputeHash(file1Stream);
byte[] secondHash = MD5.Create().ComputeHash(file2Stream);
for (int i = 0; i < firstHash.Length; i++)
{
if (i>=secondHash.Length||firstHash[i] != secondHash[i])
return false;
}
return true;
}
}
if (i>=secondHash.Length ...
Trong trường hợp nào hai băm MD5 sẽ có độ dài khác nhau?
Điều này tôi đã tìm thấy hoạt động tốt khi so sánh độ dài đầu tiên mà không cần đọc dữ liệu và sau đó so sánh chuỗi byte đọc
private static bool IsFileIdentical(string a, string b)
{
if (new FileInfo(a).Length != new FileInfo(b).Length) return false;
return (File.ReadAllBytes(a).SequenceEqual(File.ReadAllBytes(b)));
}