dotnet C# 做一個(gè)壓縮包和解壓縮出來的文件夾內(nèi)容對比工具
本文記錄的工具能夠?qū)崿F(xiàn)以下情況的對比:
- 文件夾里面丟失了某個(gè)在壓縮包里面記錄的文件
- 文件夾里面的文件和壓縮包里面的文件的內(nèi)容不相同
- 文件夾多出了壓縮包里面沒有的文件
我這里新建的是一個(gè) .NET 9 的 WPF 應(yīng)用,只有簡單的界面,理論上核心代碼可以應(yīng)用在任何 UI 框架上,甚至控制臺項(xiàng)目也可以
界面代碼如下,非常簡單,只有兩個(gè)輸入框,用來分別輸入壓縮包文件路徑和解壓縮的文件夾路徑,和一個(gè)按鈕
<Grid>
<Grid HorizontalAlignment="Center" VerticalAlignment="Center">
<Grid.RowDefinitions>
<RowDefinition Height="Auto"></RowDefinition>
<RowDefinition Height="Auto"></RowDefinition>
<RowDefinition Height="Auto"></RowDefinition>
</Grid.RowDefinitions>
<Grid.ColumnDefinitions>
<ColumnDefinition Width="Auto"></ColumnDefinition>
<ColumnDefinition MinWidth="200"></ColumnDefinition>
</Grid.ColumnDefinitions>
<TextBlock VerticalAlignment="Center" Text="壓縮包路徑:"></TextBlock>
<TextBox x:Name="ZipFilePathTextBox" Grid.Row="0" Grid.Column="1" VerticalAlignment="Center" Text="C:\lindexi\Work\Source.zip"></TextBox>
<TextBlock Grid.Row="1" Grid.Column="0" Margin="0,10,0,0" VerticalAlignment="Center" Text="解壓縮文件夾:"></TextBlock>
<TextBox x:Name="UnzipFolderPathTextBox" Grid.Row="1" Grid.Column="1" Margin="0,10,0,0" VerticalAlignment="Center" Text="C:\lindexi\Work\Unzip\"></TextBox>
<Button Grid.Row="2" Grid.Column="1" Margin="10,10,0,10" HorizontalAlignment="Right" Content="對比" Click="Button_OnClick"></Button>
</Grid>
</Grid>
我編寫了一個(gè) ZipComparer 類型,用這個(gè)類型可以用來對比壓縮包文件和解壓縮文件夾里面的內(nèi)容,按鈕的 Button_OnClick 代碼如下
private async void Button_OnClick(object sender, RoutedEventArgs e)
{
try
{
var zipCompareOptions = new ZipCompareOptions(ReturnFast: true, IgnoreExtra: false);
var result = await ZipComparer.Compare(new FileInfo(ZipFilePathTextBox.Text), new DirectoryInfo(UnzipFolderPathTextBox.Text), zipCompareOptions);
if (result.IsSuccess)
{
MessageBox.Show("文件一致");
}
else
{
var stringBuilder = new StringBuilder();
stringBuilder.AppendLine("文件不一致");
foreach (var item in result.DifferenceList)
{
stringBuilder.AppendLine(item.RelativeFilePath);
stringBuilder.AppendLine(item.DifferenceType.ToString());
}
MessageBox.Show(stringBuilder.ToString());
}
}
catch
{
// 忽略
}
}
核心代碼是 ZipComparer 類,代碼如下
public static class ZipComparer
{
public static async Task<ZipCompareResult> Compare(FileInfo zipFile, DirectoryInfo unzipFolder, ZipCompareOptions options)
{
HashSet<string/*RelativePath*/>? visitedFileSet = null;
if (!options.IgnoreExtra)
{
visitedFileSet = [];
}
List<ZipCompareDifferenceFileInfo>? differenceList = null;
await using var fileStream = zipFile.OpenRead();
using var zipArchive = new System.IO.Compression.ZipArchive(fileStream, System.IO.Compression.ZipArchiveMode.Read, leaveOpen: true);
foreach (var zipArchiveEntry in zipArchive.Entries)
{
var name = zipArchiveEntry.FullName;
visitedFileSet?.Add(name);
var filePath = Path.Join(unzipFolder.FullName, name);
if (!File.Exists(filePath))
{
// 文件不存在
AddDifference(new ZipCompareDifferenceFileInfo(name, ZipCompareDifferenceType.Miss));
if (options.ReturnFast)
{
break;
}
else
{
continue;
}
}
var fileInfo = new FileInfo(filePath);
if (fileInfo.Length != zipArchiveEntry.Length)
{
// 文件大小不同
AddDifference(new ZipCompareDifferenceFileInfo(name, ZipCompareDifferenceType.ContentLengthDifference));
if (options.ReturnFast)
{
break;
}
else
{
continue;
}
}
// 先不引入 Crc32 對比邏輯。要真正想用,需要引入 System.IO.Hashing 包
//var crc32 = zipArchiveEntry.Crc32;
//if (crc32 != 0 && Crc32.IsSupported)
//{
// var fileCrc32 = await GetFileZipCrcAsync(fileInfo);
// if (crc32 == fileCrc32)
// {
// continue;
// }
// else
// {
// // CRC32 不同
// AddDifference(new ZipCompareDifferenceFileInfo(name, ZipCompareDifferenceType.ContentDifference));
// if (options.ReturnFast)
// {
// break;
// }
// else
// {
// continue;
// }
// }
//}
// 開始對比文件內(nèi)容
await using var zipStream = zipArchiveEntry.Open();
await using var currentFileStream = fileInfo.OpenRead();
var success = await CompareStream(zipStream, currentFileStream);
if (!success)
{
// 文件內(nèi)容不同
AddDifference(new ZipCompareDifferenceFileInfo(name, ZipCompareDifferenceType.ContentDifference));
if (options.ReturnFast)
{
break;
}
}
}
var isDifferenceFastReturn = options.ReturnFast && differenceList is { Count: > 0 };
if (!options.IgnoreExtra
// 如果是快速返回,則不需要檢查額外的文件,此時(shí)已經(jīng)存在不相同的文件
&& !isDifferenceFastReturn)
{
// 如果不能忽略額外的文件,則需要檢查解壓縮文件夾中是否有額外的文件
Debug.Assert(visitedFileSet != null);
foreach (var file in unzipFolder.EnumerateFiles("*", new EnumerationOptions()
{
RecurseSubdirectories = true
}))
{
var relativePath = Path.GetRelativePath(unzipFolder.FullName, file.FullName);
if (!visitedFileSet.Contains(relativePath))
{
// 額外的文件
AddDifference(new ZipCompareDifferenceFileInfo(relativePath, ZipCompareDifferenceType.Extra));
if (options.ReturnFast)
{
break;
}
}
}
}
return new ZipCompareResult
{
DifferenceList = differenceList,
};
void AddDifference(ZipCompareDifferenceFileInfo info)
{
differenceList ??= new List<ZipCompareDifferenceFileInfo>();
differenceList.Add(info);
}
}
//private static async ValueTask<uint> GetFileZipCrcAsync(FileInfo fileInfo)
//{
// const int bufferLength = 4 * 1024;
// var buffer = ArrayPool<byte>.Shared.Rent(bufferLength);
// uint crc = 0;
// try
// {
// await using var fileStream = fileInfo.OpenRead();
// var memory = buffer.AsMemory(0, bufferLength);
// while (true)
// {
// var readLength = await fileStream.ReadAsync(memory);
// if (readLength == 0)
// {
// return crc;
// }
// for (int i = 0; i < readLength; i++)
// {
// crc = Crc32.ComputeCrc32(crc, memory.Span[i]);
// }
// }
// }
// finally
// {
// ArrayPool<byte>.Shared.Return(buffer);
// }
//}
private static async ValueTask<bool> CompareStream(Stream a, Stream b)
{
const int bufferLength = 4 * 1024;
var bufferA = ArrayPool<byte>.Shared.Rent(bufferLength);
var bufferB = ArrayPool<byte>.Shared.Rent(bufferLength);
try
{
while (true)
{
var memoryA = bufferA.AsMemory(0, bufferLength);
var readLength = await a.ReadAsync(memoryA);
if (readLength == 0)
{
// 讀取完畢
return true;
}
var memoryB = bufferB.AsMemory(0, readLength);
await b.ReadExactlyAsync(memoryB);
if (!memoryA.Span.Slice(0, readLength).SequenceEqual(memoryB.Span))
{
return false;
}
}
}
finally
{
ArrayPool<byte>.Shared.Return(bufferA);
ArrayPool<byte>.Shared.Return(bufferB);
}
}
}
/// <summary>
/// 比較的選項(xiàng)
/// </summary>
/// <param name="ReturnFast">找到第一個(gè)差異就立刻結(jié)束</param>
/// <param name="IgnoreExtra">忽略額外的文件。忽略解壓縮文件夾存在,但壓縮包不存在的文件</param>
public readonly record struct ZipCompareOptions(bool ReturnFast, bool IgnoreExtra);
public readonly record struct ZipCompareResult
{
[MemberNotNullWhen(false, nameof(DifferenceList))]
public bool IsSuccess => DifferenceList is null || DifferenceList.Count == 0;
public IReadOnlyList<ZipCompareDifferenceFileInfo>? DifferenceList { get; internal init; }
}
public readonly record struct ZipCompareDifferenceFileInfo(string RelativeFilePath, ZipCompareDifferenceType DifferenceType);
public enum ZipCompareDifferenceType
{
/// <summary>
/// 文件不存在
/// </summary>
Miss,
/// <summary>
/// 文件大小不同
/// </summary>
ContentLengthDifference,
/// <summary>
/// 文件內(nèi)容不同
/// </summary>
ContentDifference,
/// <summary>
/// 額外的文件,即解壓縮文件夾存在,但壓縮包不存在的文件
/// </summary>
Extra,
}
本文代碼放在 github 和 gitee 上,可以使用如下命令行拉取代碼。我整個(gè)代碼倉庫比較龐大,使用以下命令行可以進(jìn)行部分拉取,拉取速度比較快
先創(chuàng)建一個(gè)空文件夾,接著使用命令行 cd 命令進(jìn)入此空文件夾,在命令行里面輸入以下代碼,即可獲取到本文的代碼
git init
git remote add origin https://gitee.com/lindexi/lindexi_gd.git
git pull origin 88e3c7fe8009b4ad239481404c2c4b5089f05953
以上使用的是國內(nèi)的 gitee 的源,如果 gitee 不能訪問,請?zhí)鎿Q為 github 的源。請?jiān)诿钚欣^續(xù)輸入以下代碼,將 gitee 源換成 github 源進(jìn)行拉取代碼。如果依然拉取不到代碼,可以發(fā)郵件向我要代碼
git remote remove origin
git remote add origin https://github.com/lindexi/lindexi_gd.git
git pull origin 88e3c7fe8009b4ad239481404c2c4b5089f05953
獲取代碼之后,進(jìn)入 WPFDemo/CardawnarheaCahichemga 文件夾,即可獲取到源代碼
更多技術(shù)博客,請參閱 博客導(dǎo)航
博客園博客只做備份,博客發(fā)布就不再更新,如果想看最新博客,請?jiān)L問 https://blog.lindexi.com/
如圖片看不見,請?jiān)跒g覽器開啟不安全http內(nèi)容兼容

本作品采用知識共享署名-非商業(yè)性使用-相同方式共享 4.0 國際許可協(xié)議進(jìn)行許可。歡迎轉(zhuǎn)載、使用、重新發(fā)布,但務(wù)必保留文章署名[林德熙](http://www.rzrgm.cn/lindexi)(包含鏈接:http://www.rzrgm.cn/lindexi ),不得用于商業(yè)目的,基于本文修改后的作品務(wù)必以相同的許可發(fā)布。如有任何疑問,請與我[聯(lián)系](mailto:lindexi_gd@163.com)。

浙公網(wǎng)安備 33010602011771號