Skip to content

Instantly share code, notes, and snippets.

@neuecc
Last active April 14, 2024 08:46
Show Gist options
  • Star 6 You must be signed in to star a gist
  • Fork 2 You must be signed in to fork a gist
  • Save neuecc/97cc0ff3970941ba4b7c9594e93998b2 to your computer and use it in GitHub Desktop.
Save neuecc/97cc0ff3970941ba4b7c9594e93998b2 to your computer and use it in GitHub Desktop.
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;
}
}
@filzrev
Copy link

filzrev commented Nov 30, 2023

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))

Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment