Last active
April 14, 2024 08:46
-
-
Save neuecc/97cc0ff3970941ba4b7c9594e93998b2 to your computer and use it in GitHub Desktop.
This file contains hidden or bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
public static class FileComparer | |
{ | |
public static bool CompareEquals(string filePath1, string filePath2, int bufferSize = 65536) | |
{ | |
if (filePath1 == filePath2) return true; | |
var buffer1 = ArrayPool<byte>.Shared.Rent(bufferSize); | |
var buffer2 = ArrayPool<byte>.Shared.Rent(bufferSize); | |
try | |
{ | |
using var handle1 = File.OpenHandle(filePath1, FileMode.Open, FileAccess.Read, options: FileOptions.SequentialScan); | |
using var handle2 = File.OpenHandle(filePath2, FileMode.Open, FileAccess.Read, options: FileOptions.SequentialScan); | |
var length1 = RandomAccess.GetLength(handle1); | |
var length2 = RandomAccess.GetLength(handle2); | |
if (length1 == 0 && length2 == 0) return true; | |
if (length1 != length2) return false; | |
long fileOffset = 0; | |
while (fileOffset != length1) | |
{ | |
var read1 = ReadFully(handle1, fileOffset, buffer1); | |
var read2 = ReadFully(handle2, fileOffset, buffer2); | |
if (read1 != read2) return false; | |
if (!buffer1.AsSpan(0, read1).SequenceEqual(buffer2.AsSpan(0, read2))) | |
{ | |
return false; | |
} | |
fileOffset += read1; | |
} | |
return true; | |
} | |
finally | |
{ | |
ArrayPool<byte>.Shared.Return(buffer1); | |
ArrayPool<byte>.Shared.Return(buffer2); | |
} | |
} | |
public static async ValueTask<bool> CompareEqualsAsync(string filePath1, string filePath2, int bufferSize = 65536, CancellationToken cancellationToken = default) | |
{ | |
if (filePath1 == filePath2) return true; | |
var buffer1 = ArrayPool<byte>.Shared.Rent(bufferSize); | |
var buffer2 = ArrayPool<byte>.Shared.Rent(bufferSize); | |
try | |
{ | |
using var handle1 = File.OpenHandle(filePath1, FileMode.Open, FileAccess.Read, options: FileOptions.SequentialScan | FileOptions.Asynchronous); | |
using var handle2 = File.OpenHandle(filePath2, FileMode.Open, FileAccess.Read, options: FileOptions.SequentialScan | FileOptions.Asynchronous); | |
var length1 = RandomAccess.GetLength(handle1); | |
var length2 = RandomAccess.GetLength(handle2); | |
if (length1 == 0 && length2 == 0) return true; | |
if (length1 != length2) return false; | |
long fileOffset = 0; | |
while (fileOffset != length1) | |
{ | |
// inlined ReadFully | |
int read1 = 0; | |
{ | |
var offset = fileOffset; | |
var buffer = buffer1.AsMemory(); | |
var bufferLength = buffer1.Length; | |
while (read1 < bufferLength) | |
{ | |
var read = await RandomAccess.ReadAsync(handle1, buffer, offset, cancellationToken).ConfigureAwait(false); | |
if (read == 0) break; | |
read1 += read; | |
offset += read; | |
buffer = buffer.Slice(read); | |
} | |
} | |
int read2 = 0; | |
{ | |
var offset = fileOffset; | |
var buffer = buffer2.AsMemory(); | |
var bufferLength = buffer2.Length; | |
while (read2 < bufferLength) | |
{ | |
var read = await RandomAccess.ReadAsync(handle2, buffer, offset, cancellationToken).ConfigureAwait(false); | |
if (read == 0) break; | |
read2 += read; | |
offset += read; | |
buffer = buffer.Slice(read); | |
} | |
} | |
if (read1 != read2) return false; | |
if (!buffer1.AsSpan(0, read1).SequenceEqual(buffer2.AsSpan(0, read2))) | |
{ | |
return false; | |
} | |
fileOffset += read1; | |
} | |
return true; | |
} | |
finally | |
{ | |
ArrayPool<byte>.Shared.Return(buffer1); | |
ArrayPool<byte>.Shared.Return(buffer2); | |
} | |
} | |
[MethodImpl(MethodImplOptions.AggressiveInlining)] | |
static int ReadFully(SafeFileHandle handle, long fileOffset, Span<byte> buffer) | |
{ | |
int bytesRead = 0; | |
var bufferLength = buffer.Length; | |
while (bytesRead < bufferLength) | |
{ | |
var read = RandomAccess.Read(handle, buffer, fileOffset); | |
if (read == 0) return bytesRead; | |
bytesRead += read; | |
fileOffset += read; | |
buffer = buffer.Slice(read); | |
} | |
return bytesRead; | |
} | |
} |
Sign up for free
to join this conversation on GitHub.
Already have an account?
Sign in to comment
If file's length changed frequently.
It's faster to get file's length by new
new FileInfo(filePath).Length
.Because it can skip
File.OpenHandle
API call if the file length is different.(But it requires extra memory allocation to call
new FileInfo(filePath)
)