在C#中处理ZIP压缩包,基础能力由.NET内置的System.IO.Compression命名空间提供,无需引入第三方库即可完成大部分解压任务。该命名空间包含两个核心类:ZipFile和ZipArchive。ZipFile提供静态方法,适合快速整包解压;ZipArchive则允许以流的方式操作压缩包内部结构,适合需要精细控制的场景。本文将围绕这两个类展开,从简单到复杂,逐步讲解C#解压ZIP文件的完整思路。

快速上手:使用ZipFile类一键解压
如果你的需求只是把整个ZIP压缩包原封不动地解压到某个目录,那么ZipFile.ExtractToDirectory是最直接的选择。这个方法位于System.IO.Compression命名空间,使用静态调用即可,不需要手动管理文件流和压缩包对象。下面是一段最基本的解压代码:
using System.IO.Compression; string zipPath = @"C:\Temp\archive.zip"; string extractPath = @"C:\Temp\extracted"; ZipFile.ExtractToDirectory(zipPath, extractPath);
这段代码会把archive.zip中的所有文件和目录结构原样还原到extracted目录下。需要注意的是,如果目标目录已经存在,默认调用会抛出IOException异常,因为该重载不允许覆盖现有文件。如果你希望在目录已存在时直接覆盖同名文件,可以使用带第三个参数overwriteFiles的重载版本:
ZipFile.ExtractToDirectory(zipPath, extractPath, true);
第三个参数设为true后,解压过程中遇到同名文件会直接覆盖,不再抛出异常。这适合批量处理场景,但也意味着如果压缩包内文件与目标目录中重要文件同名,会被静默替换。因此建议在使用覆盖模式之前,先确认目标目录的状态,或者将解压目标指向一个临时目录再手动合并。
除了从磁盘路径解压,ZipFile还支持从Stream中读取压缩包,例如从网络下载流或内存流中直接解压。这在处理用户上传的ZIP文件时非常有用,可以省去先保存到临时文件的步骤。
深入控制:使用ZipArchive遍历条目
当压缩包文件数量很多,或者你只需要解压其中一部分文件时,ZipFile.ExtractToDirectory就显得不够灵活。ZipArchive类提供了对压缩包内部结构的细粒度访问,允许你逐个读取条目(ZipArchiveEntry),查看文件名、大小、压缩率等信息,并按需提取。
下面是一个使用ZipFile.OpenRead方法打开压缩包并遍历所有条目的示例。这个方法返回一个只读的ZipArchive实例,适合解压场景。代码中会根据扩展名过滤出所有txt文件,然后创建对应的目录结构并提取文件。
using System.IO.Compression;
using (ZipArchive archive = ZipFile.OpenRead(zipPath))
{
foreach (ZipArchiveEntry entry in archive.Entries)
{
Console.WriteLine($"条目名称: {entry.FullName}, 压缩前大小: {entry.Length}, 压缩后大小: {entry.CompressedLength}");
if (entry.FullName.EndsWith(".txt", StringComparison.OrdinalIgnoreCase))
{
string destinationPath = Path.Combine(extractPath, entry.FullName);
Directory.CreateDirectory(Path.GetDirectoryName(destinationPath)!);
entry.ExtractToFile(destinationPath, true);
}
}
}
这段代码展示了几个关键点:首先,archive.Entries返回压缩包中的所有条目,每个条目的FullName属性包含相对路径;其次,提取前需要手动创建目标目录,因为ExtractToFile不会自动创建父目录;最后,ExtractToFile的第二个参数同样控制是否覆盖已有文件。
通过遍历条目,你可以轻松实现进度显示、选择性解压、跳过已存在文件,甚至可以一边读取一边记录日志。对于需要解压到不同目录结构的业务逻辑,这种方式比整包解压灵活得多。此外,ZipArchive还支持直接读取单个条目的内容流,无需落盘即可处理内存中的数据。
解决中文文件名与编码问题
不少开发者在使用ZipFile.ExtractToDirectory解压包含中文文件名的ZIP包时,遇到过文件名字符变成乱码的情况。这个问题的根源在于ZIP格式本身对文件名编码没有强制规定,压缩工具可能使用UTF-8,也可能使用本地代码页(如GBK)。.NET的ZipFile静态方法在打开压缩包时使用默认编码,如果与压缩包实际编码不一致,就会导致乱码。
要解决这个问题,需要绕过ZipFile的静态方法,改为手动创建ZipArchive并指定条目名称使用的编码。ZipArchive的构造函数有一个entryNameEncoding参数,专门用于处理这种场景。示例如下:
using System.IO.Compression;
using System.Text;
Encoding.RegisterProvider(CodePagesEncodingProvider.Instance);
string zipPath = @"C:\Temp\中文压缩包.zip";
string extractPath = @"C:\Temp\extracted";
using (FileStream fs = File.OpenRead(zipPath))
using (ZipArchive archive = new ZipArchive(fs, ZipArchiveMode.Read, false, Encoding.GetEncoding("GBK")))
{
foreach (ZipArchiveEntry entry in archive.Entries)
{
string destPath = Path.Combine(extractPath, entry.FullName);
Directory.CreateDirectory(Path.GetDirectoryName(destPath)!);
entry.ExtractToFile(destPath, true);
}
}
在.NET Core或.NET 5及以上版本中,Encoding.GetEncoding("GBK")需要先通过Encoding.RegisterProvider注册代码页提供程序,否则会抛出NotSupportedException。如果你确定压缩包使用UTF-8编码,也可以直接传入Encoding.UTF8。需要注意的是,传入的编码必须与压缩包创建时使用的编码一致,否则仍然会出现乱码。
如果压缩包由多种工具生成,无法预先知道编码,可以采用一种折中方案:先尝试使用UTF-8解压,如果文件名出现替换字符(U+FFFD),再回退到GBK或系统默认编码重新打开。虽然不能保证100%正确,但在实际项目中已经能覆盖大部分场景。
流式解压与大文件处理
解压大体积ZIP文件时,直接将整个文件读入内存会带来巨大的内存压力,甚至导致OutOfMemoryException。合理的做法是基于文件流创建ZipArchive,让压缩包数据在磁盘上按需读取。上面的编码示例已经使用了FileStream,这就是流式读取的典型方式。ZipArchive在读取条目内容时会自动从底层流中按块读取,不会一次性加载整个压缩包。
还有一种常见情况是ZIP数据来自网络流或用户上传的HTTP请求体,这时可以直接使用Stream对象作为数据源,而无需先保存到临时文件。不过需要注意,如果使用MemoryStream承载整个压缩包数据,仍然会一次性占用内存,因此只适合较小的压缩包。对于大文件,建议使用FileStream或直接处理请求流。
using (Stream stream = File.OpenRead(zipPath))
using (ZipArchive archive = new ZipArchive(stream, ZipArchiveMode.Read))
{
foreach (ZipArchiveEntry entry in archive.Entries)
{
if (entry.Length > 1024 * 1024 * 100)
{
continue; // 跳过超过100MB的单文件
}
string destPath = Path.Combine(extractPath, entry.FullName);
Directory.CreateDirectory(Path.GetDirectoryName(destPath)!);
entry.ExtractToFile(destPath, true);
}
}
此外,.NET还提供了异步版本的方法,例如在.NET 6及以上版本中可以使用ExtractToFileAsync和ZipFile.ExtractToDirectoryAsync,避免在UI线程中阻塞。异步解压对于桌面应用和Web服务尤为重要,可以让程序在解压大文件时保持响应。
安全与防御性编程
解压ZIP文件时,安全问题往往容易被忽视。恶意构造的压缩包可能包含带有../路径的条目,试图将文件写入目标目录之外,这就是著名的Zip Slip漏洞。攻击者可以通过上传一个包含..\..\Windows\System32\evil.dll这类路径的ZIP文件,诱导服务器解压到任意位置,甚至覆盖系统关键文件。
要防止这类攻击,最有效的办法是校验每个条目解压后的完整路径是否仍然位于目标目录之下。下面这段代码使用了Path.GetFullPath来规范化路径,并检查路径前缀是否匹配。
string fullDestDirPath = Path.GetFullPath(extractPath);
using (ZipArchive archive = ZipFile.OpenRead(zipPath))
{
foreach (ZipArchiveEntry entry in archive.Entries)
{
string fullDestPath = Path.GetFullPath(Path.Combine(extractPath, entry.FullName));
if (!fullDestPath.StartsWith(fullDestDirPath, StringComparison.Ordinal))
{
throw new InvalidOperationException("检测到路径穿越攻击");
}
Directory.CreateDirectory(Path.GetDirectoryName(fullDestPath)!);
entry.ExtractToFile(fullDestPath, true);
}
}
这段代码在每次提取前都会检查规范化后的完整路径是否以目标目录的完整路径开头。如果压缩包条目中包含了../,Path.GetFullPath会将其解析到目标目录之外,从而触发异常。需要注意的是,字符串比较必须使用StringComparison.Ordinal,确保在Linux等大小写敏感系统上也能正确判断。
除了路径穿越,解压时还应该限制压缩包的总大小和单文件大小,避免解压炸弹耗尽磁盘空间。可以在遍历条目时检查entry.Length,或者设置总解压大小的上限。这些防御措施对于处理用户上传的ZIP文件至关重要。
通过本文的介绍,你应该已经掌握了从简单整包解压到精细控制、从编码处理到安全防护的完整知识。根据实际项目需求选择合适的解压方案,可以让C#处理ZIP文件既高效又安全。
C#解压ZIP文件ZipArchiveSystem.IO.Compression修改时间:2026-10-01 23:32:26