go 压缩与解压文件夹

458 阅读3分钟

开启掘金成长之旅!这是我参与「掘金日新计划 · 2 月更文挑战」的第 16 天,点击查看活动详情

Zip

ZIP 是一种常见的文件压缩格式,其压缩原理主要是通过将多个文件或目录组成一个归档文件,再对这个归档文件进行压缩来实现的。

在 ZIP 中,归档文件中的每个文件都被压缩成一个单独的数据块,这个数据块中包含了该文件的数据以及该文件的元数据(如文件名、文件修改时间、文件权限等)。归档文件中还包含了一个目录,记录了归档文件中包含的每个文件的元数据。

ZIP 中使用的压缩算法通常是 DEFLATE 算法,它是一种基于哈夫曼编码和LZ77算法的无损压缩算法。DEFLATE 算法可以通过对输入数据进行匹配和替换来实现数据的压缩,从而减小归档文件的大小。在 ZIP 中,DEFLATE 算法通常是在每个文件的数据块上独立应用的,这样可以保证在解压缩时,只需要解压缩需要的文件,而不需要解压缩整个归档文件。

总之,ZIP 的压缩原理主要是将多个文件或目录组成一个归档文件,并对归档文件中的每个文件进行压缩,从而减小文件的大小。这样可以更方便地在网络上传输或在存储设备上存储和管理文件。

压缩

// Zip 把路径的文件夹zip压缩
func Zip(path string) error {
   // 1. Create a ZIP file and zip.Writer
   f, err := os.Create(path + ".zip")
   if err != nil {
      return err
   }
   defer f.Close()

   writer := zip.NewWriter(f)
   defer writer.Close()

   // 2. Go through all the files of the source
   return filepath.Walk(path, func(path string, info os.FileInfo, err error) error {
      if err != nil {
         return err
      }

      // 3. Create a local file header
      header, err := zip.FileInfoHeader(info)
      if err != nil {
         return err
      }

      // set compression
      header.Method = zip.Deflate

      // 4. Set relative path of a file as the header name
      header.Name, err = filepath.Rel(filepath.Dir(path), path)
      if err != nil {
         return err
      }
      if info.IsDir() {
         header.Name += "/"
      }

      // 5. Create writer for the file header and save content of the file
      headerWriter, err := writer.CreateHeader(header)
      if err != nil {
         return err
      }

      if info.IsDir() {
         return nil
      }

      f, err := os.Open(path)
      if err != nil {
         return err
      }
      defer f.Close()

      _, err = io.Copy(headerWriter, f)
      return err
   })
}

解压缩


// 原地解压缩
func UnZip(src, dst string) (err error) {
   // 打开压缩文件,这个 zip 包有个方便的 ReadCloser 类型
   // 这个里面有个方便的 OpenReader 函数,可以比 tar 的时候省去一个打开文件的步骤
   zr, err := zip.OpenReader(src)
   defer zr.Close()
   if err != nil {
      return
   }

   // 如果解压后不是放在当前目录就按照保存目录去创建目录
   //if dst != "" {
   // if err := os.MkdirAll(dst, 0755); err != nil {
   //    return err
   // }
   //}

   // 遍历 zr ,将文件写入到磁盘
   for _, file := range zr.File {
      path := filepath.Join(dst, file.Name)

      // 如果是目录,就创建目录
      if file.FileInfo().IsDir() {
         if err := os.MkdirAll(path, file.Mode()); err != nil {
            return err
         }
         // 因为是目录,跳过当前循环,因为后面都是文件的处理
         continue
      }

      // 获取到 Reader
      fr, err := file.Open()
      if err != nil {
         return err
      }

      // 创建要写出的文件对应的 Write
      fw, err := os.OpenFile(path, os.O_CREATE|os.O_RDWR|os.O_TRUNC, file.Mode())
      if err != nil {
         return err
      }

      //n, err := io.Copy(fw, fr)
      if err != nil {
         return err
      }

      // 将解压的结果输出
      //fmt.Printf("成功解压 %s ,共写入了 %d 个字符的数据\n", path, n)

      // 因为是在循环中,无法使用 defer ,直接放在最后
      // 不过这样也有问题,当出现 err 的时候就不会执行这个了,
      // 可以把它单独放在一个函数中,这里是个实验,就这样了
      fw.Close()
      fr.Close()
   }
   return nil
}

注意事项

用压缩代码压缩和解压没什么异常,但是windows电脑压缩的zip,解压后中文文件名就乱码了,需要加一步编码的控制。