วิธีการทั่วไปแนะนำให้อ่านไบนารีผ่าน FileStream และเปรียบเทียบแบบไบต์ต่อไบต์
- การเปรียบเทียบ checksum เช่น CRC จะเร็วกว่าหรือไม่?
- มีไลบรารี. NET ที่สามารถสร้างการตรวจสอบสำหรับไฟล์หรือไม่
วิธีการทั่วไปแนะนำให้อ่านไบนารีผ่าน FileStream และเปรียบเทียบแบบไบต์ต่อไบต์
คำตอบ:
การเปรียบเทียบเช็คซัมมักจะช้ากว่าการเปรียบเทียบแบบไบต์ต่อไบต์
ในการสร้างการตรวจสอบคุณจะต้องโหลดไฟล์แต่ละไบต์และดำเนินการประมวลผล จากนั้นต้องทำในไฟล์ที่สอง การประมวลผลเกือบจะช้ากว่าการตรวจสอบเปรียบเทียบ
สำหรับการสร้างการตรวจสอบ: คุณสามารถทำได้อย่างง่ายดายด้วยคลาสการเข้ารหัส นี่คือตัวอย่างสั้น ๆ ของการสร้างการตรวจสอบ MD5ด้วย C #
อย่างไรก็ตามการตรวจสอบอาจเร็วกว่าและเหมาะสมกว่าหากคุณสามารถคำนวณผลการตรวจสอบของกรณี "ทดสอบ" หรือ "ฐาน" ล่วงหน้าได้ หากคุณมีไฟล์ที่มีอยู่และคุณกำลังตรวจสอบเพื่อดูว่าไฟล์ใหม่เหมือนกับไฟล์ที่มีอยู่หรือไม่การคำนวณเช็คซัมบนไฟล์ "ที่มีอยู่" ของคุณล่วงหน้าหมายความว่าต้องทำ DiskIO เพียงครั้งเดียวใน ไฟล์ใหม่ สิ่งนี้น่าจะเร็วกว่าการเปรียบเทียบแบบไบต์ต่อไบต์
วิธีที่ช้าที่สุดคือเปรียบเทียบไฟล์สองไฟล์ทีละไบต์ เร็วที่สุดที่ฉันสามารถหามาได้คือการเปรียบเทียบที่คล้ายกัน แต่แทนที่จะใช้ทีละไบต์คุณจะใช้อาร์เรย์ของไบต์ที่มีขนาดเท่ากับ Int64 จากนั้นเปรียบเทียบตัวเลขที่ได้
นี่คือสิ่งที่ฉันคิดขึ้น:
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;
}
ในการทดสอบของฉันฉันสามารถเห็นประสิทธิภาพที่ดีกว่าสถานการณ์ ReadByte () ที่ตรงไปตรงมาเกือบ 3: 1 เฉลี่ยมากกว่า 1,000 รันฉันได้วิธีนี้ที่ 1,063ms และวิธีการด้านล่าง (การเปรียบเทียบไบต์แบบตรงไปตรงมาโดยไบต์) ที่ 3031ms การแฮชมักจะกลับมาเป็นวินาทีย่อยที่ค่าเฉลี่ย 865 มิลลิวินาที การทดสอบนี้ใช้ไฟล์วิดีโอ ~ 100MB
นี่คือ ReadByte และวิธีการแฮชที่ฉันใช้เพื่อการเปรียบเทียบ:
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
วิธีการควรจะมีusing
ในไฟล์ทั้งลำธารเกินไปเช่นReadByte
วิธีการมิฉะนั้นจะแขวนในทั้งสองไฟล์
FileStream.Read()
อาจอ่านไบต์น้อยกว่าตัวเลขที่ร้องขอ คุณควรใช้StreamReader.ReadBlock()
แทน
หากคุณไม่ตัดสินใจว่าคุณอย่างแท้จริงต้องมีการเปรียบเทียบไบต์โดยไบต์เต็มรูปแบบ (ดูคำตอบอื่น ๆ สำหรับการอภิปรายของคร่ำเครียด) แล้วทางออกที่ง่ายที่สุดคือ:
public static bool AreFileContentsEqual(String path1, String path2) =>
File.ReadAllBytes(path1).SequenceEqual(File.ReadAllBytes(path2));
public static bool AreFileContentsEqual(FileInfo fi1, FileInfo fi2) =>
fi1.Length == fi2.Length &&
(fi1.Length == 0 || File.ReadAllBytes(fi1.FullName).SequenceEqual(
File.ReadAllBytes(fi2.FullName)));
ไม่เหมือนบางคำตอบที่โพสต์อื่น ๆ นี้เป็นสรุปที่ถูกต้องสำหรับชนิดของไฟล์ใด ๆ :ไบนารี, ข้อความ, สื่อปฏิบัติการ ฯลฯ แต่เป็นเต็มรูปแบบไบนารีเปรียบเทียบไฟล์ที่แตกต่างกันเพียงในรูปแบบ "ไม่สำคัญ" (เช่นBOM , สาย -ending , การเข้ารหัสอักขระเมสื่อ, ความเห็นช่องว่างช่องว่างภายในซอร์สโค้ด ฯลฯ ) มักจะได้รับการพิจารณาที่ไม่เท่ากัน
รหัสนี้จะโหลดไฟล์ทั้งในหน่วยความจำอย่างสิ้นเชิงดังนั้นจึงควรไม่สามารถใช้สำหรับการเปรียบเทียบขนาดยักษ์อย่างแท้จริงไฟล์ นอกเหนือจากข้อแม้ที่สำคัญแล้วการโหลดแบบเต็มไม่ใช่บทลงโทษจากการออกแบบ. NET GC (เนื่องจากได้รับการปรับให้เหมาะสมโดยพื้นฐานเพื่อให้การจัดสรรที่มีขนาดเล็กและมีอายุการใช้งานสั้นราคาถูกมาก ) และในความเป็นจริงอาจเหมาะสมที่สุดเมื่อคาดว่าจะมีขนาดไฟล์ จะน้อยกว่า85Kเพราะใช้ต่ำสุดของรหัสผู้ใช้ (ดังแสดงที่นี่) หมายถึงการมอบหมายที่สุดปัญหาประสิทธิภาพการทำงานไฟล์ไปCLR
, BCL
และJIT
ที่จะได้รับประโยชน์จาก (เช่น) เทคโนโลยีใหม่ล่าสุดที่ออกแบบรหัสระบบและการเพิ่มประสิทธิภาพรันไทม์การปรับตัว
นอกจากนี้สำหรับสถานการณ์การทำงานในวันทำงานเช่นนี้ความกังวลเกี่ยวกับประสิทธิภาพของการเปรียบเทียบแบบไบต์ต่อไบต์ผ่านตัวLINQ
นับ (ดังที่แสดงไว้ที่นี่) เป็นสิ่งที่น่าสงสัยเนื่องจากการกดปุ่มa̲t̲ a̲l̲l̲สำหรับไฟล์ I / O จะทำให้แคระแกร็นตามลำดับความสำคัญหลายขนาดประโยชน์ ของทางเลือกในการเปรียบเทียบหน่วยความจำต่างๆ ยกตัวอย่างเช่นแม้ว่าSequenceEqual
ไม่ในความเป็นจริงให้เรา "การเพิ่มประสิทธิภาพ" ของทิ้งในไม่ตรงกันแรกนี้แทบจะไม่สำคัญหลังจากที่มีการเรียกแล้วเนื้อหาไฟล์แต่ละจำเป็นอย่างเต็มที่เพื่อยืนยันการแข่งขัน ..
นอกจากคำตอบของReed Copsey แล้ว :
กรณีที่แย่ที่สุดคือไฟล์ทั้งสองไฟล์เหมือนกัน ในกรณีนี้ควรเปรียบเทียบไฟล์แบบไบต์ต่อไบต์
หากไฟล์ทั้งสองไฟล์ไม่เหมือนกันคุณสามารถเร่งความเร็วขึ้นเล็กน้อยโดยตรวจพบเร็วกว่าว่าไฟล์ไม่เหมือนกัน
ตัวอย่างเช่นหากไฟล์ทั้งสองมีความยาวต่างกันคุณจะรู้ว่าไฟล์นั้นไม่สามารถเหมือนกันได้และคุณไม่จำเป็นต้องเปรียบเทียบเนื้อหาจริงของไฟล์นั้นด้วยซ้ำ
จะเร็วยิ่งขึ้นถ้าคุณไม่ได้อ่านเป็นชิ้นเล็ก ๆ 8 ไบต์ แต่วนรอบอ่านชิ้นใหญ่ขึ้น ฉันลดเวลาเปรียบเทียบเฉลี่ยลงเหลือ 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
ไม่ถูกต้อง Stream.Read()
สามารถส่งคืนได้น้อยกว่าจำนวนที่คุณระบุไว้ด้วยเหตุผลหลายประการ
Int64
const int bufferSize = 1024 * sizeof(Int64)
สิ่งเดียวที่อาจทำให้การเปรียบเทียบ checksum เร็วกว่าการเปรียบเทียบแบบไบต์ต่อไบต์เล็กน้อยคือการที่คุณกำลังอ่านทีละไฟล์ซึ่งจะช่วยลดเวลาในการค้นหาสำหรับหัวดิสก์ได้บ้าง การเพิ่มขึ้นเล็กน้อยนั้นอาจถูกกินได้เป็นอย่างดีตามเวลาที่เพิ่มขึ้นของการคำนวณแฮช
นอกจากนี้การเปรียบเทียบเช็คซัมมีโอกาสที่จะเร็วขึ้นหากไฟล์เหมือนกัน หากไม่เป็นเช่นนั้นการเปรียบเทียบแบบไบต์ต่อไบต์จะสิ้นสุดที่ความแตกต่างแรกทำให้เร็วขึ้นมาก
นอกจากนี้คุณควรพิจารณาด้วยว่าการเปรียบเทียบรหัสแฮชจะบอกคุณเพียงว่ามีโอกาสมากที่ไฟล์จะเหมือนกัน เพื่อให้แน่ใจ 100% คุณต้องทำการเปรียบเทียบแบบไบต์ต่อไบต์
หากรหัสแฮชเป็น 32 บิตคุณจะแน่ใจได้ประมาณ 99.99999998% ว่าไฟล์นั้นเหมือนกันหากรหัสแฮชตรงกัน นั่นใกล้เคียง 100% แต่ถ้าคุณต้องการความมั่นใจ 100% อย่างแท้จริงนั่นไม่ใช่เลย
1 - (1 / (2^32))
ซึ่งเป็นความน่าจะเป็นที่ไฟล์ใดไฟล์หนึ่งจะมีแฮช 32 บิตที่กำหนด ความน่าจะเป็นของไฟล์ที่แตกต่างกันสองไฟล์ที่มีแฮชเดียวกันจะเหมือนกันเนื่องจากไฟล์แรกให้ค่าแฮช "ที่กำหนด" และเราจำเป็นต้องพิจารณาว่าไฟล์อื่นตรงกับค่านั้นหรือไม่ โอกาสในการแฮช 64- และ 128 บิตลดลงเหลือ 99.9999999999999994% และ 99.9999999999999999999999999999999999997% (ตามลำดับ) ราวกับว่ามีความสำคัญกับตัวเลขที่เข้าใจไม่ได้
แก้ไข:วิธีนี้ใช้ไม่ได้กับการเปรียบเทียบไฟล์ไบนารี!
ใน. NET 4.0 File
คลาสมีสองวิธีใหม่ดังต่อไปนี้:
public static IEnumerable<string> ReadLines(string path)
public static IEnumerable<string> ReadLines(string path, Encoding encoding)
ซึ่งหมายความว่าคุณสามารถใช้:
bool same = File.ReadLines(path1).SequenceEqual(File.ReadLines(path2));
จริงๆแล้วฉันคิดว่าคุณต้องตัดต้นไม้การค้นหาของคุณลงให้มากที่สุด
สิ่งที่ต้องตรวจสอบก่อนไปทีละไบต์:
นอกจากนี้การอ่านบล็อกขนาดใหญ่ในแต่ละครั้งจะมีประสิทธิภาพมากขึ้นเนื่องจากไดรฟ์อ่านไบต์ตามลำดับได้เร็วขึ้น การทำงานแบบไบต์ต่อไบต์ไม่เพียงทำให้เกิดการเรียกระบบมากขึ้นเท่านั้น แต่ยังทำให้หัวอ่านของฮาร์ดไดรฟ์แบบเดิมค้นหาไปมาบ่อยขึ้นหากไฟล์ทั้งสองอยู่ในไดรฟ์เดียวกัน
อ่านก้อน A และก้อน B ลงในบัฟเฟอร์ไบต์แล้วเปรียบเทียบ (อย่าใช้ Array.Equals ดูความคิดเห็น) ปรับขนาดของบล็อกจนกว่าคุณจะบรรลุสิ่งที่คุณรู้สึกว่าเป็นการแลกเปลี่ยนที่ดีระหว่างหน่วยความจำและประสิทธิภาพ คุณยังสามารถเปรียบเทียบแบบมัลติเธรดได้ แต่อย่ามัลติเธรดดิสก์อ่าน
คำตอบของฉันเป็นอนุพันธ์ของ @lars Stream.Read
แต่แก้ไขข้อผิดพลาดในการเรียกร้องให้ ฉันยังเพิ่มการตรวจสอบเส้นทางด่วนที่มีคำตอบอื่น ๆ และการตรวจสอบความถูกต้องของอินพุต ในระยะสั้นนี้ควรจะคำตอบ:
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;
}
}
}
}
}
}
หรือถ้าคุณอยากเจ๋งสุด ๆ คุณสามารถใช้ตัวแปร 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;
}
}
}
}
}
}
การทดลองของฉันแสดงให้เห็นว่าการเรียกใช้ Stream.ReadByte () จำนวนครั้งน้อยลงอย่างแน่นอน แต่การใช้ BitConverter เพื่อบรรจุไบต์ไม่ได้สร้างความแตกต่างมากนักเมื่อเทียบกับการเปรียบเทียบไบต์ในอาร์เรย์ไบต์
ดังนั้นจึงเป็นไปได้ที่จะแทนที่ "Math.Ceiling and การวนซ้ำ" ในความคิดเห็นด้านบนด้วยอันที่ง่ายที่สุด:
for (int i = 0; i < count1; i++)
{
if (buffer1[i] != buffer2[i])
return false;
}
ฉันเดาว่ามันเกี่ยวข้องกับข้อเท็จจริงที่ว่า BitConverter ToInt64 ต้องทำงานสักหน่อย (ตรวจสอบอาร์กิวเมนต์แล้วทำการขยับบิต) ก่อนที่คุณจะเปรียบเทียบและจบลงด้วยการทำงานในปริมาณเท่ากันเมื่อเปรียบเทียบ 8 ไบต์ในสองอาร์เรย์ .
หากไฟล์ไม่ใหญ่เกินไปคุณสามารถใช้:
public static byte[] ComputeFileHash(string fileName)
{
using (var stream = File.OpenRead(fileName))
return System.Security.Cryptography.MD5.Create().ComputeHash(stream);
}
จะเปรียบเทียบแฮชได้ก็ต่อเมื่อแฮชมีประโยชน์ในการจัดเก็บ
(แก้ไขรหัสเป็นสิ่งที่สะอาดกว่ามาก)
การปรับปรุงอีกประการหนึ่งของไฟล์ขนาดใหญ่ที่มีความยาวเท่ากันอาจจะไม่ใช่การอ่านไฟล์ตามลำดับ แต่ควรเปรียบเทียบบล็อกสุ่มมากกว่าหรือน้อยกว่า
คุณสามารถใช้หลายเธรดโดยเริ่มจากตำแหน่งต่างๆในไฟล์และเปรียบเทียบไปข้างหน้าหรือข้างหลัง
ด้วยวิธีนี้คุณสามารถตรวจจับการเปลี่ยนแปลงที่กลาง / ท้ายของไฟล์ได้เร็วกว่าที่คุณจะไปถึงจุดนั้นโดยใช้วิธีการตามลำดับ
หากคุณต้องการเปรียบเทียบสองไฟล์เท่านั้นฉันเดาว่าวิธีที่เร็วที่สุดน่าจะเป็น (ใน C ฉันไม่รู้ว่ามันใช้ได้กับ. NET)
OTOH หากคุณต้องการค้นหาว่ามีไฟล์ที่ซ้ำกันในชุดของไฟล์ N หรือไม่วิธีที่เร็วที่สุดคือการใช้แฮชเพื่อหลีกเลี่ยงการเปรียบเทียบ N-way bit-by-bit อย่างไม่ต้องสงสัย
บางสิ่ง (หวังว่า) จะมีประสิทธิภาพพอสมควร:
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;
}
}
ต่อไปนี้เป็นฟังก์ชันยูทิลิตี้บางอย่างที่ช่วยให้คุณตรวจสอบได้ว่าไฟล์สองไฟล์ (หรือสองสตรีม) มีข้อมูลที่เหมือนกันหรือไม่
ฉันได้จัดเตรียมเวอร์ชัน "เร็ว" ที่เป็นแบบมัลติเธรดเนื่องจากเปรียบเทียบอาร์เรย์ไบต์ (แต่ละบัฟเฟอร์เติมจากสิ่งที่อ่านในแต่ละไฟล์) ในเธรดที่แตกต่างกันโดยใช้ Tasks
ตามที่คาดไว้มันเร็วกว่ามาก (เร็วขึ้นประมาณ 3 เท่า) แต่ใช้ CPU มากขึ้น (เพราะเป็นแบบมัลติเธรด) และหน่วยความจำมากขึ้น (เนื่องจากต้องการบัฟเฟอร์อาร์เรย์สองไบต์ต่อเธรดการเปรียบเทียบ)
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;
}
ฉันคิดว่ามีแอปพลิเคชันที่ "แฮช" เร็วกว่าการเปรียบเทียบไบต์ไบต์ หากคุณต้องการเปรียบเทียบไฟล์กับผู้อื่นหรือมีภาพขนาดย่อของรูปภาพที่สามารถเปลี่ยนแปลงได้ ขึ้นอยู่กับว่าใช้ที่ไหนและอย่างไร
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;
}
ที่นี่คุณจะได้รับสิ่งที่เร็วที่สุด
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));
เราสามารถบันทึกแฮชในฐานข้อมูลได้
หวังว่านี่จะช่วยได้
สิ่งนี้ฉันพบว่าใช้งานได้ดีเมื่อเปรียบเทียบกับความยาวก่อนโดยไม่ต้องอ่านข้อมูลจากนั้นเปรียบเทียบลำดับการอ่านไบต์
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)));
}
อีกคำตอบที่ได้มาจาก @chsh MD5 พร้อมการใช้งานและทางลัดสำหรับไฟล์เดียวกันไม่มีไฟล์และความยาวต่างกัน:
/// <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 ...
แฮช MD5 สองอันมีความยาวต่างกันภายใต้สถานการณ์ใด