用 ZipArchive
打开 ZIP 并直接读取指定文件流
不需要解压到磁盘,
ZipArchive(来自
System.IO.Compression)支持只读打开 ZIP,并按路径名定位内部文件。关键在于用
ZipArchiveMode.Read打开,再通过
GetEntry(string)获取目标项,最后调用
Open()得到可读流。
常见错误是误用
ExtractToFile或尝试把整个 ZIP 加载进内存再解析——既慢又占资源。正确做法是流式访问:
ZipArchive必须用
using管理,否则底层
FileStream可能被锁住 路径名必须和 ZIP 内实际路径完全一致(区分大小写,且用正斜杠
/,即使 Windows 打包也建议统一用
/) 如果文件不存在,
GetEntry返回
null,不抛异常,需手动判空
using (var archive = new ZipArchive(fileStream, ZipArchiveMode.Read))
{
var entry = archive.GetEntry("data/config.json");
if (entry != null)
{
using (var stream = entry.Open())
using (var reader = new StreamReader(stream))
{
string content = reader.ReadToEnd();
}
}
}
从文件路径构造 FileStream
时注意 FileShare.Read
直接传
new FileStream(path, FileMode.Open)很容易在多线程或重复读取时触发“文件正由另一进程使用”错误。ZIP 包本质是随机访问的二进制文件,多个
ZipArchive实例可能同时读同一文件。
解决方案是显式指定共享模式:
务必加FileShare.Read,否则第二次读会失败 避免用
File.OpenRead(),它默认不共享 如果 ZIP 文件可能被外部修改,还需考虑加
FileOptions.RandomAccess提升性能
using (var fs = new FileStream(zipPath, FileMode.Open, FileAccess.Read, FileShare.Read))
using (var archive = new ZipArchive(fs, ZipArchiveMode.Read))
{
// ...
}
处理嵌套路径、中文名和 ZIP64 兼容性
路径中含中文或深层嵌套(如
folder/sub/abc.txt)一般没问题,但要注意三点: ZIP 中文名依赖打包工具是否写入了 UTF-8 标志位;.NET 6+ 默认启用
ZipArchive的 UTF-8 解码,旧版本(.NET Framework 4.8 / .NET Core 3.1)需手动设置
useAsync = false并确保系统区域设置匹配,否则乱码 嵌套路径不用额外处理,
GetEntry支持完整路径字符串,无需逐级找
ZipArchiveEntry超大 ZIP(>4GB)需 ZIP64 支持,
ZipArchive在 .NET Core 2.1+ 和 .NET 5+ 中原生支持,但 .NET Framework 4.8 需确认运行时补丁已安装,否则
BadImageFormatException或静默失败
想读取多个小文件?别反复新建 ZipArchive
每次
new ZipArchive都要解析中央目录,对含数百个文件的 ZIP 来说,反复打开同一 ZIP 读不同文件效率极低。应该复用一个实例: 把
ZipArchive作为 long-lived 对象缓存(例如用
ConcurrentDictionary<string ziparchive></string>),注意线程安全 不要在
using块外返回
ZipArchiveEntry.Open()的流——该流生命周期绑定于
ZipArchive实例 若需异步读,用
entry.Open().CopyToAsync(dest),但
ZipArchive本身不支持 async 构造,所以打开阶段仍是同步阻塞
真正容易被忽略的是:ZIP 文件头损坏或中央目录偏移错位时,
ZipArchive不会立刻报错,而是在第一次调用
GetEntry或遍历
Entries时才抛
InvalidDataException。线上服务最好加一层快速校验逻辑,比如先读前 4 字节是否为
PK\x03\x04。
