登录
推荐 文章 Go 技术 课程 下载 专题 AI
首页 >  Golang >  Go教程

Go archive/zip 如何只读取压缩包中央目录信息

来源:17golang原创

时间:2026-09-12 19:01:43 231浏览 收藏

如果任务只是列出 ZIP 里有哪些文件、判断大小或找出某个扩展名,没必要把每个条目的正文都解压出来。Go 的 archive/zip 已经把中央目录解析成 Reader.File,遍历它就能拿到 FileHeader 中的元信息;只有命中目标后,才调用 File.Open 读取正文。

要点速览
  • zip.OpenReaderzip.NewReader 会建立 ZIP Reader,清单在 Reader.File
  • FileHeader 适合读取名称、目录标识、压缩方式、压缩前后大小和修改时间。
  • 筛选元数据时不要调用 File.Open;正文读取要检查错误并关闭返回的流。

读取中央目录就能完成大多数清单任务

ZIP 的中央目录保存了每个条目的名称、压缩方式、压缩大小、原始大小、CRC 和修改时间等字段。archive/zip 的 Reader 初始化时会定位并解析这部分目录,所以清单阶段可以只遍历 r.File,并不会因为读取 f.Name 就展开文件正文。

本地 ZIP 文件使用 OpenReader 最直接;输入已经在内存或实现了随机读取时,则用 NewReader,同时传入准确的字节数:

package main

import (
	"archive/zip"
	"fmt"
	"log"
)

func main() {
	// OpenReader 负责打开文件并解析 ZIP 的中央目录。
	r, err := zip.OpenReader("assets.zip")
	if err != nil {
		log.Fatal(err)
	}
	defer r.Close() // ReadCloser 持有底层文件句柄,函数结束时释放。

	for _, f := range r.File {
		// 这里只访问清单对象,不调用 f.Open() 读取压缩正文。
		fmt.Println(f.Name)
	}
}
Go archive/zip 中 zip.OpenReader、中央目录、Reader.File 与 FileHeader 的静态关系示意
图1:中央目录解析后形成 Reader.File 清单;遍历元数据不等于打开条目正文的结构示意图。

这里的“只读取”是业务层面的读取边界:程序仍需从 ZIP 尾部定位中央目录并解析目录记录,但不会启动条目解压。目录损坏、文件不是合法 ZIP 或路径安全检查失败时,Reader 初始化仍可能返回错误。

从 FileHeader 提取条目元信息

zip.File 嵌入了 FileHeader,因此可以直接读取字段。清单程序通常先排除目录,再用 64 位大小字段做配额判断,避免旧的 32 位字段在大文件上溢出或显示为截断值。

字段用途判断提示
Name条目路径和文件名/ 结尾通常表示目录
Method压缩方式可用于显示或策略筛选,不代表正文已读取
CompressedSize64压缩后大小估算归档占用
UncompressedSize64解压后大小做展开前的容量检查
Modified条目修改时间用于清单展示或排序
for _, f := range r.File {
	// 目录也会出现在清单中,先排除再统计文件条目。
	if f.FileInfo().IsDir() {
		continue
	}
	// 64 位字段适用于大于 4 GiB 的条目,避免依赖旧字段。
	fmt.Printf("%s method=%d compressed=%d raw=%d modified=%s\n",
		f.Name, f.Method, f.CompressedSize64, f.UncompressedSize64,
		f.Modified.Format(time.RFC3339))
}

上面的片段需要额外导入 time。如果只做目录清单,也可以省略时间格式化。不要把 UncompressedSize64 当成已经分配的内存,它只是目录里声明的大小,真正展开前仍应设置自己的容量上限。

只有命中目标后才打开条目正文

元数据筛选和正文读取最好分成两个阶段。f.Open() 返回 io.ReadCloser,之后读取才会经过对应的解压器,并在读取过程中承担校验职责。若只是判断名称,调用它反而会增加 I/O 和 CPU 成本。

import (
	"io"
	"strings"
)

func readManifest(r *zip.Reader) ([]byte, error) {
	for _, f := range r.File {
		// 先用中央目录筛选目标,其他条目完全不打开。
		if f.Name != "manifest.json" || strings.HasSuffix(f.Name, "/") {
			continue
		}
		// 命中后才取得正文流,并确保所有返回路径都关闭它。
		rc, err := f.Open()
		if err != nil {
			return nil, err
		}
		body, readErr := io.ReadAll(rc)
		closeErr := rc.Close()
		if readErr != nil {
			return nil, readErr
		}
		if closeErr != nil {
			return nil, closeErr
		}
		return body, nil
	}
	return nil, fmt.Errorf("manifest.json not found")
}
Go archive/zip 从 zip.File 元数据筛选到 File.Open、io.ReadCloser、解压器和 CRC 校验的静态关系示意
图2:先在 FileHeader 层筛选,命中条目后才进入 File.Open 和正文流;这是元数据与内容读取的边界示意图。

输入来源和安全边界要提前分清

文件路径、*os.File 或内存字节都必须支持随机读取,才能让 Reader 按目录偏移访问 ZIP。只有顺序到来的网络流并不适合直接套 NewReader;应先落盘、缓存到支持 io.ReaderAt 的对象,或选择专门的流式格式。

另外,当前 Go 的 OpenReaderNewReader 可能因为条目名称包含反斜杠或非本地路径返回 zip.ErrInsecurePath。即使只看中央目录,也应把它当作输入检查结果处理;不要把所有错误都改成“忽略后继续”。

任务推荐动作不要做什么
列出条目遍历 Reader.File为每个条目调用 Open
检查展开风险比较 UncompressedSize64 与业务上限仅相信压缩后大小
读取一个文件名称命中后再 File.Open把目录字段当正文
处理外部 ZIP处理 ErrFormatErrInsecurePath无条件解压到目标目录

常见问题

遍历 r.File 会不会自动解压?

不会。遍历主要访问已解析的中央目录记录;真正读取条目内容通常从调用 File.Open 并读取返回流开始。

为什么 NewReader 还要传 size?

ZIP 的目录位置需要结合数据范围定位,NewReader 接收的是支持随机读取的对象和它的准确字节数。

只看大小就能保证解压安全吗?

不能。大小只能帮助限制容量,路径仍要检查,解压时还要限制输出、拒绝不安全名称并处理格式与校验错误。

声明:本文转载于:17golang原创 如有侵犯,请联系study_golang@163.com删除
相关阅读
更多>
最新阅读
更多>
课程推荐
更多>