Keyboard shortcuts

Press or to navigate between chapters

Press S or / to search in the book

Press ? to show this help

Press Esc to hide this help

Go 语言标准库 —— archive/zip 包(zip 归档处理)


🔹 常量

存储方法(无压缩)

zip.Store

  • 值:0,表示不压缩直接存储

  • 示例

    package main
    
    import (
    	"archive/zip"
    	"fmt"
    )
    
    func main() {
    	// 创建 zip 文件
    	zipFile, _ := zip.Create("test.zip")
    	defer zipFile.Close()
    	
    	// 使用 Store 方法(不压缩)
    	writer := zip.NewWriter(zipFile)
    	
    	header := &zip.Header{
    		Name:   "file.txt",
    		Method: zip.Store, // 不压缩
    	}
    	
    	fmt.Printf("压缩方法:%d (0=Store, 8=Deflate)\n", header.Method)
    }
    

Deflate 压缩方法

zip.Deflate

  • 值:8,使用 Deflate 算法压缩

  • 示例

    package main
    
    import (
    	"archive/zip"
    	"fmt"
    )
    
    func main() {
    	header := &zip.Header{
    		Name:   "compressed.txt",
    		Method: zip.Deflate, // 使用 Deflate 压缩
    	}
    	
    	fmt.Printf("使用 Deflate 压缩:%d\n", header.Method)
    }
    

🔹 类型

zip.File

zip.File struct

  • 说明:表示 zip 归档中的一个文件

  • 字段:

    • FileHeader - 文件头
    • compressedMethod uint16 - 压缩方法
    • compressedSize uint32 - 压缩后大小
    • uncompressedSize uint32 - 压缩前大小
    • Reader - 读取器
  • 示例

    package main
    
    import (
    	"archive/zip"
    	"fmt"
    	"io"
    	"os"
    )
    
    func main() {
    	// 打开 zip 文件
    	r, err := zip.OpenReader("test.zip")
    	if err != nil {
    		fmt.Println("打开失败:", err)
    		return
    	}
    	defer r.Close()
    	
    	// 遍历文件
    	for _, f := range r.File {
    		fmt.Printf("文件:%s\n", f.Name)
    		fmt.Printf("  压缩方法:%d\n", f.Method)
    		fmt.Printf("  压缩后大小:%d\n", f.CompressedSize64)
    		fmt.Printf("  原始大小:%d\n", f.UncompressedSize64)
    		
    		// 读取内容
    		rc, err := f.Open()
    		if err != nil {
    			fmt.Println("打开失败:", err)
    			continue
    		}
    		
    		content, _ := io.ReadAll(rc)
    		rc.Close()
    		
    		fmt.Printf("  内容:%s\n", string(content))
    	}
    }
    

zip.FileHeader

zip.FileHeader struct

  • 说明:zip 文件的文件头信息,包含文件的元数据

  • 字段详解:

    • Name string - 文件名或路径(支持相对路径和绝对路径)
    • Method uint16 - 压缩方法(zip.Store=0 不压缩,zip.Deflate=8)
    • Modified time.Time - 最后修改时间
    • CRC32 uint32 - CRC32 校验值(用于验证数据完整性)
    • CompressedSize64 uint64 - 压缩后的大小(字节)
    • UncompressedSize64 uint64 - 压缩前的原始大小(字节)
    • ExternalAttrs []byte - 外部文件属性(如 Unix 权限)
    • InternalAttrs uint16 - 内部文件属性
    • Comment string - 文件注释
    • Extra []byte - 额外字段(用于扩展信息)
    • CreatorVersion uint16 - 创建者版本
    • ReaderVersion uint16 - 读取所需版本
    • Flags uint16 - 标志位
  • 注意事项:

    • Name 字段应使用正斜杠(/)作为路径分隔符
    • 对于目录,Name 通常以 / 结尾
    • Method 必须是 zip.Store 或 zip.Deflate
    • Modified 时间会被转换为 DOS 格式存储
    • CRC32 在写入数据后自动计算
  • 示例(完整)

    package main
    
    import (
    	"archive/zip"
    	"fmt"
    	"time"
    )
    
    func main() {
    	header := &zip.FileHeader{
    		Name:     "test.txt",
    		Method:   zip.Deflate,
    		Modified: time.Now(),
    	}
    	
    	fmt.Printf("文件名:%s\n", header.Name)
    	fmt.Printf("压缩方法:%d\n", header.Method)
    	fmt.Printf("修改时间:%v\n", header.Modified)
    }
    
  • 使用场景示例

    • 创建普通文件头

      • 示例:
        header := &zip.FileHeader{
        	Name:     "file.txt",
        	Method:   zip.Deflate,
        	Modified: time.Now(),
        }
        
    • 创建目录头

      • 示例:
        header := &zip.FileHeader{
        	Name: "mydir/",
        }
        
    • 使用 FileInfoHeader 创建

      • 示例:
        info, _ := os.Stat("file.txt")
        header, _ := zip.FileInfoHeader(info, "")
        header.Method = zip.Deflate
        
    • 设置自定义时间

      • 示例:
        header.Modified = time.Date(2024, 1, 1, 12, 0, 0, 0, time.UTC)
        

zip.Reader

zip.Reader struct

  • 说明:用于读取 zip 归档

  • 字段:

    • File []*File - 文件列表(按顺序排列)
    • Comment string - zip 归档的注释
  • 常用方法详解

    • Open 方法

      • 说明:打开 zip 中的文件进行读取
      • 方法:(f *File) Open() (io.ReadCloser, error)
      • 注意:返回的 ReadCloser 需要关闭
      • 示例:
        rc, err := file.Open()
        if err != nil {
        	return err
        }
        defer rc.Close()
        
    • FileInfo 方法

      • 说明:返回文件的 FileInfo 接口
      • 方法:(f *File) FileInfo() fs.FileInfo
      • 注意:用于判断是否为目录、获取权限等
      • 示例:
        info := file.FileInfo()
        if info.IsDir() {
        	// 是目录
        }
        
  • 示例(完整)

    package main
    
    import (
    	"archive/zip"
    	"fmt"
    	"io"
    	"strings"
    )
    
    func main() {
    	// 从内存创建 zip 读取器
    	data := []byte("模拟 zip 数据...")
    	reader := strings.NewReader(string(data))
    	
    	// 创建 zip 读取器
    	r, err := zip.NewReader(reader, int64(len(data)))
    	if err != nil {
    		fmt.Println("创建失败:", err)
    		return
    	}
    	
    	fmt.Printf("文件数量:%d\n", len(r.File))
    	fmt.Printf("注释:%s\n", r.Comment)
    	
    	// 遍历文件
    	for _, f := range r.File {
    		fmt.Printf("文件:%s\n", f.Name)
    		
    		// 读取内容
    		rc, _ := f.Open()
    		content, _ := io.ReadAll(rc)
    		rc.Close()
    		
    		fmt.Printf("  内容:%s\n", string(content))
    	}
    }
    
  • 使用场景示例

    • 查找特定文件

      • 示例:
        r, _ := zip.OpenReader("archive.zip")
        defer r.Close()
        
        for _, f := range r.File {
        	if f.Name == "config.txt" {
        		rc, _ := f.Open()
        		defer rc.Close()
        		data, _ := io.ReadAll(rc)
        		fmt.Println(string(data))
        	}
        }
        
    • 统计压缩率

      • 示例:
        var totalCompressed, totalUncompressed uint64
        for _, f := range r.File {
        	if !f.FileInfo().IsDir() {
        		totalCompressed += f.CompressedSize64
        		totalUncompressed += f.UncompressedSize64
        	}
        }
        ratio := float64(totalCompressed) * 100 / float64(totalUncompressed)
        fmt.Printf("压缩率:%.1f%%\n", ratio)
        
    • 列出所有文件

      • 示例:
        for _, f := range r.File {
        	fileType := "文件"
        	if f.FileInfo().IsDir() {
        		fileType = "目录"
        	}
        	fmt.Printf("%s [%s] %d bytes\n", 
        		f.Name, fileType, f.UncompressedSize64)
        }
        

zip.ReadCloser

zip.ReadCloser struct

  • 说明:可关闭的 zip 读取器,嵌入了 *zip.Reader

  • 字段:

    • 继承 zip.Reader 的所有字段(File、Comment)
    • 内部包含关闭方法
  • 常用方法详解

    • Close 方法
      • 说明:关闭 zip 文件和读取器
      • 方法:Close() error
      • 注意:
        • 必须调用,释放文件句柄
        • 应该在 defer 中调用确保关闭
      • 示例:
        rc, _ := zip.OpenReader("archive.zip")
        defer rc.Close() // 确保关闭
        
  • 示例(完整)

    package main
    
    import (
    	"archive/zip"
    	"fmt"
    )
    
    func main() {
    	// 打开 zip 文件
    	rc, err := zip.OpenReader("test.zip")
    	if err != nil {
    		fmt.Println("打开失败:", err)
    		return
    	}
    	defer rc.Close()
    	
    	fmt.Printf("文件数量:%d\n", len(rc.File))
    	
    	// 遍历文件
    	for _, f := range rc.File {
    		fmt.Printf("文件:%s\n", f.Name)
    	}
    }
    
  • 使用场景示例

    • 批量读取文件

      • 示例:
        rc, _ := zip.OpenReader("archive.zip")
        defer rc.Close()
        
        for _, f := range rc.File {
        	processFile(f)
        }
        
    • 条件读取

      • 示例:
        rc, _ := zip.OpenReader("data.zip")
        defer rc.Close()
        
        for _, f := range rc.File {
        	if strings.HasSuffix(f.Name, ".txt") {
        		readTextFile(f)
        	}
        }
        

zip.Writer

zip.Writer struct

  • 说明:用于写入 zip 归档

  • 常用方法详解

    • Create 方法

      • 说明:在 zip 中创建一个新文件
      • 方法:Create(name string) (io.Writer, error)
      • 注意:
        • 使用默认设置创建文件
        • 自动处理路径分隔符(使用 /)
        • 返回的 io.Writer 用于写入文件内容
      • 示例:
        fw, err := w.Create("file.txt")
        if err != nil {
        	return err
        }
        fw.Write([]byte("content"))
        
    • CreateHeader 方法

      • 说明:使用自定义 FileHeader 创建文件
      • 方法:CreateHeader(fh *FileHeader) (io.Writer, error)
      • 注意:
        • 可以设置压缩方法、时间等属性
        • 创建目录时 Name 以 / 结尾
        • 更灵活,推荐使用
      • 示例:
        header := &zip.FileHeader{
        	Name:   "file.txt",
        	Method: zip.Deflate,
        }
        fw, _ := w.CreateHeader(header)
        fw.Write([]byte("content"))
        
    • CreateRaw 方法

      • 说明:创建文件并直接写入原始数据
      • 方法:CreateRaw(fh *FileHeader) (io.Writer, error)
      • 注意:
        • 用于写入已经压缩的数据
        • 需要手动设置 CRC32 和大小
      • 示例:
        header := &zip.FileHeader{
        	Name:               "compressed.txt",
        	Method:             zip.Deflate,
        	CompressedSize64:   size,
        	UncompressedSize64: usize,
        	CRC32:              crc,
        }
        fw, _ := w.CreateRaw(header)
        fw.Write(compressedData)
        
    • SetComment 方法

      • 说明:设置 zip 归档的注释
      • 方法:SetComment(comment string) error
      • 注意:
        • 注释长度有限制(通常 64KB)
        • 必须在 Close() 之前调用
      • 示例:
        w.SetComment("这是备份文件")
        
    • RegisterCompressor 方法

      • 说明:注册自定义压缩器
      • 方法:RegisterCompressor(method uint16, comp Compressor)
      • 注意:
        • 用于支持非标准压缩方法
        • 通常不需要使用
      • 示例:
        // 注册自定义压缩器
        zip.RegisterCompressor(zip.Deflate, func(w io.Writer) (io.WriteCloser, error) {
        	return flate.NewWriter(w, flate.DefaultCompression)
        })
        
    • Close 方法

      • 说明:完成 zip 归档写入
      • 方法:Close() error
      • 注意:
        • 必须调用,否则 zip 文件不完整
        • 会写入中央目录和结束记录
        • 应该在 defer 中调用确保关闭
      • 示例:
        w := zip.NewWriter(file)
        defer w.Close() // 确保关闭
        // 写入文件...
        
  • 示例(完整)

    package main
    
    import (
    	"archive/zip"
    	"fmt"
    	"os"
    )
    
    func main() {
    	// 创建 zip 文件
    	file, err := os.Create("archive.zip")
    	if err != nil {
    		fmt.Println("创建失败:", err)
    		return
    	}
    	defer file.Close()
    	
    	// 创建 zip 写入器
    	w := zip.NewWriter(file)
    	defer w.Close()
    	
    	// 写入文件
    	fw, _ := w.Create("hello.txt")
    	fw.Write([]byte("Hello, World!"))
    	
    	fmt.Println("zip 归档创建成功")
    }
    
  • 使用场景示例

    • 创建多个文件

      • 示例:
        w := zip.NewWriter(file)
        defer w.Close()
        
        // 文件 1
        fw1, _ := w.Create("file1.txt")
        fw1.Write([]byte("content1"))
        
        // 文件 2
        fw2, _ := w.Create("file2.txt")
        fw2.Write([]byte("content2"))
        
    • 创建目录结构

      • 示例:
        // 创建目录
        dirHeader := &zip.FileHeader{Name: "docs/"}
        w.CreateHeader(dirHeader)
        
        // 创建目录中的文件
        fw, _ := w.Create("docs/readme.txt")
        fw.Write([]byte("content"))
        
    • 设置压缩方法

      • 示例:
        header := &zip.FileHeader{
        	Name:   "data.txt",
        	Method: zip.Deflate, // 使用压缩
        }
        fw, _ := w.CreateHeader(header)
        fw.Write(data)
        
    • 写入大文件

      • 示例:
        header := &zip.FileHeader{
        	Name:   "large.bin",
        	Method: zip.Deflate,
        }
        fw, _ := w.CreateHeader(header)
        
        // 分块写入
        buf := make([]byte, 1024*1024)
        for {
        	n, _ := src.Read(buf)
        	if n == 0 {
        		break
        	}
        	fw.Write(buf[:n])
        }
        
  • 注意事项

    • 忘记 Close

      • 错误示例:
        w := zip.NewWriter(file)
        // 写入文件...
        // 忘记 Close,zip 文件损坏!
        
    • 正确的 defer 用法

      • 正确示例:
        w := zip.NewWriter(file)
        defer w.Close() // 确保关闭
        // 写入文件...
        
    • 路径分隔符

      • 注意:
        • 始终使用正斜杠(/)
        • Windows 路径需要转换
      • 示例:
        name := filepath.ToSlash(relPath)
        header.Name = name
        

🔹 函数

打开 zip 文件

zip.OpenReader(name string) (*ReadCloser, error)

  • 示例

    package main
    
    import (
    	"archive/zip"
    	"fmt"
    	"io"
    )
    
    func main() {
    	// 打开 zip 文件
    	rc, err := zip.OpenReader("test.zip")
    	if err != nil {
    		fmt.Println("打开失败:", err)
    		return
    	}
    	defer rc.Close()
    	
    	// 遍历所有文件
    	for _, f := range rc.File {
    		fmt.Printf("文件:%s\n", f.Name)
    		
    		// 跳过目录
    		if f.FileInfo().IsDir() {
    			continue
    		}
    		
    		// 读取内容
    		rc, err := f.Open()
    		if err != nil {
    			fmt.Println("打开文件失败:", err)
    			continue
    		}
    		
    		content, _ := io.ReadAll(rc)
    		rc.Close()
    		
    		fmt.Printf("  内容:%s\n", string(content))
    	}
    }
    

创建 zip 读取器

zip.NewReader(r io.ReaderAt, size int64) (*Reader, error)

  • 示例

    package main
    
    import (
    	"archive/zip"
    	"bytes"
    	"fmt"
    	"os"
    )
    
    func main() {
    	// 读取 zip 文件到内存
    	data, err := os.ReadFile("test.zip")
    	if err != nil {
    		fmt.Println("读取失败:", err)
    		return
    	}
    	
    	// 创建 ReaderAt
    	readerAt := bytes.NewReader(data)
    	
    	// 创建 zip 读取器
    	r, err := zip.NewReader(readerAt, int64(len(data)))
    	if err != nil {
    		fmt.Println("创建失败:", err)
    		return
    	}
    	
    	fmt.Printf("文件数量:%d\n", len(r.File))
    	
    	// 遍历文件
    	for _, f := range r.File {
    		fmt.Printf("文件:%s\n", f.Name)
    	}
    }
    

创建 zip 写入器

zip.NewWriter(w io.Writer) *zip.Writer

  • 示例

    package main
    
    import (
    	"archive/zip"
    	"fmt"
    	"os"
    )
    
    func main() {
    	// 创建文件
    	file, err := os.Create("new.zip")
    	if err != nil {
    		fmt.Println("创建失败:", err)
    		return
    	}
    	defer file.Close()
    	
    	// 创建 zip 写入器
    	w := zip.NewWriter(file)
    	defer w.Close()
    	
    	// 添加文件
    	fw, _ := w.Create("readme.txt")
    	fw.Write([]byte("这是一个测试文件"))
    	
    	// 添加目录
    	w.Create("docs/")
    	
    	fmt.Println("zip 归档创建成功")
    }
    

🔹 zip.File 方法

打开文件读取

(*zip.File).Open() (io.ReadCloser, error)

  • 示例

    package main
    
    import (
    	"archive/zip"
    	"fmt"
    	"io"
    	"os"
    )
    
    func main() {
    	rc, err := zip.OpenReader("test.zip")
    	if err != nil {
    		fmt.Println("打开失败:", err)
    		return
    	}
    	defer rc.Close()
    	
    	// 查找特定文件
    	for _, f := range rc.File {
    		if f.Name == "config.txt" {
    			// 打开文件
    			rc, err := f.Open()
    			if err != nil {
    				fmt.Println("打开失败:", err)
    				return
    			}
    			defer rc.Close()
    			
    			// 读取内容
    			content, err := io.ReadAll(rc)
    			if err != nil {
    				fmt.Println("读取失败:", err)
    				return
    			}
    			
    			fmt.Printf("config.txt 内容:\n%s\n", string(content))
    			return
    		}
    	}
    	
    	fmt.Println("文件未找到")
    }
    

设置密码(已废弃)

(*zip.File).SetPassword(password string)

  • 说明:Go 1.17+ 已废弃,不再支持密码功能

  • 示例

    // 注意:此方法已废弃,不推荐使用
    // 如需加密,请使用第三方库
    

🔹 zip.FileHeader 方法

创建文件头

FileInfoHeader(fi FileInfo, link string) (*FileHeader, error)

  • 示例

    package main
    
    import (
    	"archive/zip"
    	"fmt"
    	"os"
    )
    
    func main() {
    	// 获取文件信息
    	info, err := os.Stat("test.txt")
    	if err != nil {
    		fmt.Println("获取失败:", err)
    		return
    	}
    	
    	// 创建文件头
    	header, err := zip.FileInfoHeader(info, "")
    	if err != nil {
    		fmt.Println("创建失败:", err)
    		return
    	}
    	
    	// 设置压缩方法
    	header.Method = zip.Deflate
    	
    	fmt.Printf("文件名:%s\n", header.Name)
    	fmt.Printf("大小:%d\n", header.UncompressedSize64)
    	fmt.Printf("压缩方法:%d\n", header.Method)
    }
    

🔹 zip.Writer 方法

创建文件

(*zip.Writer).Create(name string) (io.Writer, error)

  • 示例

    package main
    
    import (
    	"archive/zip"
    	"fmt"
    	"os"
    )
    
    func main() {
    	file, err := os.Create("create.zip")
    	if err != nil {
    		fmt.Println("创建失败:", err)
    		return
    	}
    	defer file.Close()
    	
    	w := zip.NewWriter(file)
    	defer w.Close()
    	
    	// 创建文件
    	fw, err := w.Create("hello.txt")
    	if err != nil {
    		fmt.Println("创建失败:", err)
    		return
    	}
    	
    	// 写入内容
    	_, err = fw.Write([]byte("Hello, World!"))
    	if err != nil {
    		fmt.Println("写入失败:", err)
    		return
    	}
    	
    	fmt.Println("文件创建成功")
    }
    

创建带选项的文件

(*zip.Writer).CreateHeader(fh *FileHeader) (io.Writer, error)

  • 示例

    package main
    
    import (
    	"archive/zip"
    	"fmt"
    	"os"
    	"time"
    )
    
    func main() {
    	file, err := os.Create("header.zip")
    	if err != nil {
    		fmt.Println("创建失败:", err)
    		return
    	}
    	defer file.Close()
    	
    	w := zip.NewWriter(file)
    	defer w.Close()
    	
    	// 创建自定义文件头
    	header := &zip.FileHeader{
    		Name:     "custom.txt",
    		Method:   zip.Deflate,
    		Modified: time.Now(),
    	}
    	
    	// 创建文件
    	fw, err := w.CreateHeader(header)
    	if err != nil {
    		fmt.Println("创建失败:", err)
    		return
    	}
    	
    	// 写入内容
    	fw.Write([]byte("自定义头文件"))
    	
    	fmt.Println("创建成功")
    }
    

创建目录

(*zip.Writer).CreateHeader(fh *FileHeader) (io.Writer, error)

  • 示例

    package main
    
    import (
    	"archive/zip"
    	"fmt"
    	"os"
    )
    
    func main() {
    	file, err := os.Create("dir.zip")
    	if err != nil {
    		fmt.Println("创建失败:", err)
    		return
    	}
    	defer file.Close()
    	
    	w := zip.NewWriter(file)
    	defer w.Close()
    	
    	// 创建目录(名称以 / 结尾)
    	dirHeader := &zip.FileHeader{
    		Name: "mydir/",
    	}
    	
    	_, err = w.CreateHeader(dirHeader)
    	if err != nil {
    		fmt.Println("创建目录失败:", err)
    		return
    	}
    	
    	// 在目录中创建文件
    	fw, _ := w.Create("mydir/file.txt")
    	fw.Write([]byte("目录中的文件"))
    	
    	fmt.Println("目录创建成功")
    }
    

设置注释

(*zip.Writer).SetComment(comment string) error

  • 示例

    package main
    
    import (
    	"archive/zip"
    	"fmt"
    	"os"
    )
    
    func main() {
    	file, err := os.Create("comment.zip")
    	if err != nil {
    		fmt.Println("创建失败:", err)
    		return
    	}
    	defer file.Close()
    	
    	w := zip.NewWriter(file)
    	defer w.Close()
    	
    	// 添加文件
    	fw, _ := w.Create("readme.txt")
    	fw.Write([]byte("内容"))
    	
    	// 设置注释
    	err = w.SetComment("这是一个测试 zip 文件")
    	if err != nil {
    		fmt.Println("设置注释失败:", err)
    		return
    	}
    	
    	fmt.Println("注释设置成功")
    }
    

关闭写入器

(*zip.Writer).Close() error

  • 说明:完成 zip 归档写入,必须调用

  • 示例

    package main
    
    import (
    	"archive/zip"
    	"fmt"
    	"os"
    )
    
    func main() {
    	file, err := os.Create("final.zip")
    	if err != nil {
    		fmt.Println("创建失败:", err)
    		return
    	}
    	
    	w := zip.NewWriter(file)
    	
    	// 添加文件
    	fw, _ := w.Create("test.txt")
    	fw.Write([]byte("hello"))
    	
    	// 关闭写入器(重要!)
    	if err := w.Close(); err != nil {
    		fmt.Println("关闭失败:", err)
    		return
    	}
    	
    	file.Close()
    	
    	fmt.Println("zip 归档完成")
    }
    

🔹 实际应用示例

创建 zip 归档

  • 示例

    package main
    
    import (
    	"archive/zip"
    	"fmt"
    	"io"
    	"io/fs"
    	"os"
    	"path/filepath"
    	"strings"
    )
    
    func createZip(source, target string) error {
    	// 创建 zip 文件
    	zipFile, err := os.Create(target)
    	if err != nil {
    		return err
    	}
    	defer zipFile.Close()
    	
    	w := zip.NewWriter(zipFile)
    	defer w.Close()
    	
    	// 遍历源目录
    	return filepath.Walk(source, func(path string, info fs.FileInfo, err error) error {
    		if err != nil {
    			return err
    		}
    		
    		// 获取相对路径
    		relPath, err := filepath.Rel(source, path)
    		if err != nil {
    			return err
    		}
    		
    		// 跳过根目录
    		if relPath == "." {
    			return nil
    		}
    		
    		// 创建文件头
    		header, err := zip.FileInfoHeader(info)
    		if err != nil {
    			return err
    		}
    		
    		// 设置相对路径
    		header.Name = filepath.ToSlash(relPath)
    		header.Method = zip.Deflate // 使用压缩
    		
    		// 如果是目录,添加 / 后缀
    		if info.IsDir() {
    			header.Name += "/"
    		}
    		
    		// 写入文件头
    		fw, err := w.CreateHeader(header)
    		if err != nil {
    			return err
    		}
    		
    		// 跳过目录
    		if info.IsDir() {
    			return nil
    		}
    		
    		// 读取并写入文件内容
    		file, err := os.Open(path)
    		if err != nil {
    			return err
    		}
    		defer file.Close()
    		
    		_, err = io.Copy(fw, file)
    		return err
    	})
    }
    
    func main() {
    	err := createZip("./source", "./backup.zip")
    	if err != nil {
    		fmt.Println("创建失败:", err)
    		return
    	}
    	fmt.Println("zip 归档创建成功")
    }
    

解压 zip 归档

  • 示例

    package main
    
    import (
    	"archive/zip"
    	"fmt"
    	"io"
    	"os"
    	"path/filepath"
    	"strings"
    )
    
    func extractZip(zipFile, dest string) error {
    	// 打开 zip 文件
    	r, err := zip.OpenReader(zipFile)
    	if err != nil {
    		return err
    	}
    	defer r.Close()
    	
    	// 遍历所有文件
    	for _, f := range r.File {
    		// 构建目标路径
    		target := filepath.Join(dest, f.Name)
    		
    		// 安全检查:防止路径遍历攻击
    		if !strings.HasPrefix(target, dest) {
    			fmt.Printf("跳过不安全路径:%s\n", target)
    			continue
    		}
    		
    		// 处理目录
    		if f.FileInfo().IsDir() {
    			if err := os.MkdirAll(target, f.Mode()); err != nil {
    				return err
    			}
    			continue
    		}
    		
    		// 创建父目录
    		if err := os.MkdirAll(filepath.Dir(target), 0755); err != nil {
    			return err
    		}
    		
    		// 打开 zip 中的文件
    		rc, err := f.Open()
    		if err != nil {
    			return err
    		}
    		defer rc.Close()
    		
    		// 创建目标文件
    		outFile, err := os.Create(target)
    		if err != nil {
    			return err
    		}
    		
    		// 复制内容
    		_, err = io.Copy(outFile, rc)
    		outFile.Close()
    		
    		if err != nil {
    			return err
    		}
    		
    		// 设置权限
    		os.Chmod(target, f.Mode())
    	}
    	
    	return nil
    }
    
    func main() {
    	err := extractZip("./backup.zip", "./restored")
    	if err != nil {
    		fmt.Println("解压失败:", err)
    		return
    	}
    	fmt.Println("解压成功")
    }
    

向 zip 添加文件

  • 示例

    package main
    
    import (
    	"archive/zip"
    	"fmt"
    	"io"
    	"os"
    )
    
    func addFileToZip(zipPath, filePath, archivePath string) error {
    	// 读取现有 zip 文件
    	existingZip, err := os.Open(zipPath)
    	if err != nil {
    		// 文件不存在,创建新的
    		return createNewZip(zipPath, filePath, archivePath)
    	}
    	existingZip.Close()
    	
    	// 读取现有 zip 内容
    	r, err := zip.OpenReader(zipPath)
    	if err != nil {
    		return err
    	}
    	defer r.Close()
    	
    	// 创建临时 zip 文件
    	tempFile, err := os.Create("temp.zip")
    	if err != nil {
    		return err
    	}
    	defer tempFile.Close()
    	
    	w := zip.NewWriter(tempFile)
    	defer w.Close()
    	
    	// 复制现有文件
    	for _, f := range r.File {
    		err := copyZipFile(w, f)
    		if err != nil {
    			return err
    		}
    	}
    	
    	// 添加新文件
    	err = addNewFile(w, filePath, archivePath)
    	if err != nil {
    		return err
    	}
    	
    	w.Close()
    	r.Close()
    	
    	// 替换原文件
    	os.Remove(zipPath)
    	os.Rename("temp.zip", zipPath)
    	
    	return nil
    }
    
    func createNewZip(zipPath, filePath, archivePath string) error {
    	file, err := os.Create(zipPath)
    	if err != nil {
    		return err
    	}
    	defer file.Close()
    	
    	w := zip.NewWriter(file)
    	defer w.Close()
    	
    	return addNewFile(w, filePath, archivePath)
    }
    
    func copyZipFile(w *zip.Writer, f *zip.File) error {
    	header, _ := zip.FileInfoHeader(f.FileInfo(), "")
    	header.Name = f.Name
    	header.Method = f.Method
    	
    	fw, err := w.CreateHeader(header)
    	if err != nil {
    		return err
    	}
    	
    	if f.FileInfo().IsDir() {
    		return nil
    	}
    	
    	rc, err := f.Open()
    	if err != nil {
    		return err
    	}
    	defer rc.Close()
    	
    	_, err = io.Copy(fw, rc)
    	return err
    }
    
    func addNewFile(w *zip.Writer, filePath, archivePath string) error {
    	file, err := os.Open(filePath)
    	if err != nil {
    		return err
    	}
    	defer file.Close()
    	
    	info, err := file.Stat()
    	if err != nil {
    		return err
    	}
    	
    	header := &zip.FileHeader{
    		Name:     archivePath,
    		Method:   zip.Deflate,
    		Modified: info.ModTime(),
    	}
    	
    	fw, err := w.CreateHeader(header)
    	if err != nil {
    		return err
    	}
    	
    	_, err = io.Copy(fw, file)
    	return err
    }
    
    func main() {
    	err := addFileToZip("./archive.zip", "./newfile.txt", "docs/newfile.txt")
    	if err != nil {
    		fmt.Println("添加失败:", err)
    		return
    	}
    	fmt.Println("文件添加成功")
    }
    

读取 zip 中的特定文件

  • 示例

    package main
    
    import (
    	"archive/zip"
    	"fmt"
    	"io"
    	"os"
    	"strings"
    )
    
    func readFileFromZip(zipPath, fileName string) ([]byte, error) {
    	r, err := zip.OpenReader(zipPath)
    	if err != nil {
    		return nil, err
    	}
    	defer r.Close()
    	
    	// 查找文件
    	for _, f := range r.File {
    		if f.Name == fileName || strings.HasSuffix(f.Name, fileName) {
    			// 跳过目录
    			if f.FileInfo().IsDir() {
    				return nil, fmt.Errorf("目标是目录:%s", fileName)
    			}
    			
    			// 打开文件
    			rc, err := f.Open()
    			if err != nil {
    				return nil, err
    			}
    			defer rc.Close()
    			
    			// 读取内容
    			return io.ReadAll(rc)
    		}
    	}
    	
    	return nil, fmt.Errorf("文件未找到:%s", fileName)
    }
    
    func main() {
    	content, err := readFileFromZip("./archive.zip", "config.txt")
    	if err != nil {
    		fmt.Println("读取失败:", err)
    		return
    	}
    	
    	fmt.Printf("文件内容:\n%s\n", string(content))
    }
    

列出 zip 归档内容

  • 示例

    package main
    
    import (
    	"archive/zip"
    	"fmt"
    	"os"
    	"strings"
    )
    
    func listZipContents(zipPath string) error {
    	r, err := zip.OpenReader(zipPath)
    	if err != nil {
    		return err
    	}
    	defer r.Close()
    	
    	fmt.Printf("%-40s %-8s %-10s %-10s %s\n", 
    		"文件名", "类型", "压缩后", "原始", "压缩率")
    	fmt.Println(strings.Repeat("-", 90))
    	
    	var totalCompressed, totalUncompressed uint64
    	
    	for _, f := range r.File {
    		// 确定类型
    		fileType := "文件"
    		if f.FileInfo().IsDir() {
    			fileType = "目录"
    		}
    		
    		// 计算压缩率
    		var ratio string
    		if f.FileInfo().IsDir() {
    			ratio = "-"
    		} else {
    			ratio = fmt.Sprintf("%.1f%%", 
    				float64(f.CompressedSize64)*100/float64(f.UncompressedSize64))
    			totalCompressed += f.CompressedSize64
    			totalUncompressed += f.UncompressedSize64
    		}
    		
    		fmt.Printf("%-40s %-8s %-10d %-10d %s\n",
    			f.Name, fileType, f.CompressedSize64, f.UncompressedSize64, ratio)
    	}
    	
    	fmt.Println(strings.Repeat("-", 90))
    	if totalUncompressed > 0 {
    		totalRatio := float64(totalCompressed) * 100 / float64(totalUncompressed)
    		fmt.Printf("总计:%d -> %d (压缩率:%.1f%%)\n", 
    			totalUncompressed, totalCompressed, totalRatio)
    	}
    	
    	return nil
    }
    
    func main() {
    	err := listZipContents("./backup.zip")
    	if err != nil {
    		fmt.Println("列出失败:", err)
    		return
    	}
    }
    

压缩和解压完整示例

  • 示例

    package main
    
    import (
    	"archive/zip"
    	"fmt"
    	"io"
    	"io/fs"
    	"os"
    	"path/filepath"
    	"strings"
    )
    
    // 压缩目录
    func compressDir(source, target string) error {
    	zipFile, err := os.Create(target)
    	if err != nil {
    		return err
    	}
    	defer zipFile.Close()
    	
    	w := zip.NewWriter(zipFile)
    	defer w.Close()
    	
    	return filepath.Walk(source, func(path string, info fs.FileInfo, err error) error {
    		if err != nil {
    			return err
    		}
    		
    		relPath, err := filepath.Rel(source, path)
    		if err != nil {
    			return err
    		}
    		
    		if relPath == "." {
    			return nil
    		}
    		
    		header, err := zip.FileInfoHeader(info)
    		if err != nil {
    			return err
    		}
    		
    		header.Name = filepath.ToSlash(relPath)
    		header.Method = zip.Deflate
    		
    		if info.IsDir() {
    			header.Name += "/"
    		}
    		
    		fw, err := w.CreateHeader(header)
    		if err != nil {
    			return err
    		}
    		
    		if info.IsDir() {
    			return nil
    		}
    		
    		file, err := os.Open(path)
    		if err != nil {
    			return err
    		}
    		defer file.Close()
    		
    		_, err = io.Copy(fw, file)
    		return err
    	})
    }
    
    // 解压 zip 文件
    func decompressZip(zipFile, dest string) error {
    	r, err := zip.OpenReader(zipFile)
    	if err != nil {
    		return err
    	}
    	defer r.Close()
    	
    	for _, f := range r.File {
    		target := filepath.Join(dest, f.Name)
    		
    		if !strings.HasPrefix(target, dest) {
    			continue
    		}
    		
    		if f.FileInfo().IsDir() {
    			os.MkdirAll(target, f.Mode())
    			continue
    		}
    		
    		os.MkdirAll(filepath.Dir(target), 0755)
    		
    		rc, err := f.Open()
    		if err != nil {
    			return err
    		}
    		
    		outFile, err := os.Create(target)
    		if err != nil {
    			rc.Close()
    			return err
    		}
    		
    		_, err = io.Copy(outFile, rc)
    		outFile.Close()
    		rc.Close()
    		
    		if err != nil {
    			return err
    		}
    		
    		os.Chmod(target, f.Mode())
    	}
    	
    	return nil
    }
    
    func main() {
    	// 压缩
    	fmt.Println("压缩目录...")
    	err := compressDir("./source", "./backup.zip")
    	if err != nil {
    		fmt.Println("压缩失败:", err)
    		return
    	}
    	fmt.Println("压缩成功")
    	
    	// 解压
    	fmt.Println("\n解压文件...")
    	err = decompressZip("./backup.zip", "./restored")
    	if err != nil {
    		fmt.Println("解压失败:", err)
    		return
    	}
    	fmt.Println("解压成功")
    }
    

创建自解压 zip(高级)

  • 示例

    package main
    
    import (
    	"archive/zip"
    	"fmt"
    	"io"
    	"os"
    )
    
    // 创建带自解压头的 zip(Windows)
    func createSFXZip(files []string, outputPath string) error {
    	// 读取 SFX 模块(需要单独的 sfx.exe 文件)
    	sfxModule, err := os.ReadFile("sfx.exe")
    	if err != nil {
    		return fmt.Errorf("需要 sfx.exe 模块:%v", err)
    	}
    	
    	// 创建输出文件
    	outFile, err := os.Create(outputPath)
    	if err != nil {
    		return err
    	}
    	defer outFile.Close()
    	
    	// 写入 SFX 模块
    	outFile.Write(sfxModule)
    	
    	// 创建 zip 写入器
    	w := zip.NewWriter(outFile)
    	defer w.Close()
    	
    	// 添加文件
    	for _, filePath := range files {
    		file, err := os.Open(filePath)
    		if err != nil {
    			return err
    		}
    		
    		info, err := file.Stat()
    		if err != nil {
    			file.Close()
    			return err
    		}
    		
    		header, err := zip.FileInfoHeader(info)
    		if err != nil {
    			file.Close()
    			return err
    		}
    		
    		header.Method = zip.Deflate
    		
    		fw, err := w.CreateHeader(header)
    		if err != nil {
    			file.Close()
    			return err
    		}
    		
    		io.Copy(fw, file)
    		file.Close()
    	}
    	
    	return nil
    }
    
    func main() {
    	files := []string{"app.exe", "config.ini", "readme.txt"}
    	
    	err := createSFXZip(files, "installer.exe")
    	if err != nil {
    		fmt.Println("创建失败:", err)
    		return
    	}
    	
    	fmt.Println("自解压文件创建成功")
    	fmt.Println("注意:需要 sfx.exe 模块才能创建真正的自解压文件")
    }
    

🔥 总结

核心类型

  • zip.File 👉 zip 中的文件
  • zip.FileHeader 👉 文件头信息
  • zip.Reader 👉 读取 zip 归档
  • zip.ReadCloser 👉 可关闭的读取器
  • zip.Writer 👉 写入 zip 归档

常用函数

  • zip.OpenReader() 👉 打开 zip 文件
  • zip.NewReader() 👉 创建读取器
  • zip.NewWriter() 👉 创建写入器

压缩方法

  • zip.Store 👉 不压缩(方法 0)
  • zip.Deflate 👉 Deflate 压缩(方法 8)

关键方法

  • Open() 👉 打开文件读取
  • Create() 👉 创建文件
  • CreateHeader() 👉 自定义创建文件
  • SetComment() 👉 设置注释
  • Close() 👉 关闭写入器
  • FileInfoHeader() 👉 从 FileInfo 创建头

实际应用场景

  • 文件打包和分发
  • 备份和归档
  • 软件安装包
  • 文档压缩传输
  • 日志归档
  • 资源打包(游戏、应用资源)

与 tar 的区别

  • zip:跨平台、支持压缩、自带目录结构
  • tar:Unix 标准、通常配合 gzip 使用、保留更多文件属性