Golang中如何解决使用archive/zip包的OpenReader读取大文件(80GB)失败的问题

Golang中如何解决使用archive/zip包的OpenReader读取大文件(80GB)失败的问题 我在使用 zip包 - archive/zip - Go Packages 时遇到了问题。当文件容量超过 80GB 时,使用 OpenReader() 函数会报错:

zip: not a valid zip file
exit status 1

但同样的操作对于 700 MB 的文件却能正常工作并成功解压。

2 回复

你确定是文件大小的问题,而不是Zip归档文件格式错误吗?你可以在Go问题跟踪器上提一个issue。你也可以尝试使用其他库,比如 mholt/archives,看看它是否对格式不那么挑剔。举个例子,archive/zip 中的代码有这样的注释:

if strings.HasSuffix(f.Name, "/") {
	// The ZIP specification (APPNOTE.TXT) specifies that directories, which
	// are technically zero-byte files, must not have any associated file
	// data. We previously tried failing here if f.CompressedSize64 != 0,
	// but it turns out that a number of implementations (namely, the Java
	// jar tool) don't properly set the storage method on directories
	// resulting in a file with compressed size > 0 but uncompressed size ==
	// 0. We still want to fail when a directory has associated uncompressed
	// data, but we are tolerant of cases where the uncompressed size is
	// zero but compressed size is not.
	if f.UncompressedSize64 != 0 {
		return &dirReader{ErrFormat}, nil
	} else {
		return &dirReader{io.EOF}, nil
	}
}

所以,可能你使用的归档工具做了一些他们没预料到的事情,从而引发了那个错误,等等。你知道你试图解压的归档文件是由哪个归档工具创建的吗?也可能是文件条目数量的限制:

原始的 .ZIP 格式对各种参数有 4 GB (2^32 字节) 的限制(单个文件的未压缩大小、压缩大小以及归档文件的总大小),并且 ZIP 归档中的条目数量限制为 65,535 (2^16-1) 个。在规范版本 4.5 中(这不同于任何特定工具的 v4.5 版本),PKWARE 引入了“ZIP64”格式扩展来绕过这些限制,将限制提高到 16 EB (2^64 字节)。本质上,它为一个文件使用一个“普通”的中央目录条目,后跟一个可选的“zip64”目录条目,后者包含更大的字段。[40]

来源:ZIP (file format) - Wikipedia

更多关于Golang中如何解决使用archive/zip包的OpenReader读取大文件(80GB)失败的问题的实战系列教程也可以访问 https://www.itying.com/category-94-b0.html


在Go的archive/zip包中,OpenReader()确实有文件大小限制。这是因为标准库的zip实现使用了int32作为偏移量,最大支持2GB-1的文件。对于80GB的大文件,需要采用流式处理方式。

以下是使用Reader接口处理大ZIP文件的示例:

package main

import (
    "archive/zip"
    "fmt"
    "io"
    "os"
)

func extractLargeZip(zipPath, destDir string) error {
    // 打开文件但不立即读取整个目录
    file, err := os.Open(zipPath)
    if err != nil {
        return err
    }
    defer file.Close()

    // 获取文件信息
    fi, err := file.Stat()
    if err != nil {
        return err
    }

    // 使用ReaderAt接口创建zip.Reader
    reader, err := zip.NewReader(file, fi.Size())
    if err != nil {
        return fmt.Errorf("failed to create zip reader: %v", err)
    }

    // 遍历并解压文件
    for _, f := range reader.File {
        if err := extractFile(f, destDir); err != nil {
            return err
        }
    }
    
    return nil
}

func extractFile(f *zip.File, destDir string) error {
    rc, err := f.Open()
    if err != nil {
        return err
    }
    defer rc.Close()

    // 创建目标文件
    path := destDir + "/" + f.Name
    if f.FileInfo().IsDir() {
        return os.MkdirAll(path, f.Mode())
    }

    // 确保目录存在
    if err := os.MkdirAll(destDir+"/"+filepath.Dir(f.Name), 0755); err != nil {
        return err
    }

    outFile, err := os.OpenFile(path, os.O_WRONLY|os.O_CREATE|os.O_TRUNC, f.Mode())
    if err != nil {
        return err
    }
    defer outFile.Close()

    // 流式复制文件内容
    _, err = io.Copy(outFile, rc)
    return err
}

func main() {
    if err := extractLargeZip("large_file.zip", "./output"); err != nil {
        fmt.Printf("Error: %v\n", err)
        os.Exit(1)
    }
    fmt.Println("Extraction completed successfully")
}

对于特别大的ZIP文件,还可以考虑使用支持64位扩展的第三方库:

import "github.com/yeka/zip"

func extractZip64(zipPath, destDir string) error {
    r, err := zip.OpenReader(zipPath)
    if err != nil {
        return err
    }
    defer r.Close()
    
    // 处理逻辑与上面类似
    // ...
    return nil
}

关键点:

  1. 使用os.Open()配合zip.NewReader()替代zip.OpenReader()
  2. 流式处理每个文件,避免内存溢出
  3. 对于超过4GB的单个文件,确保使用支持ZIP64的库
  4. 处理过程中注意及时关闭文件描述符
回到顶部