archive/zip 实现打包压缩及解压
背景知识
ZIP的作者是一个叫Phil Katz的人。Phil Katz这个人是个牛逼程序员,成名于DOS时代,计算机早期网速很慢,拨号使用的是只有几十Kb(比特不是字节)的猫,56Kb实际上是这种猫的最高速度,在ADSL出现之后,这种技术被迅速淘汰。当时记录文件的也是硬盘,但是在电脑之间拷贝文件的是软盘,最高容量记得是1.44MB,这还是200X年的软盘,以前的软盘容量具体多大就不知道了,Phil Katz上网的时候还不到1990年,WWW实际上就没出现,浏览器当然是没有的,当时上网干嘛呢?基本就是类似于网管敲各种命令,这样实际上也可以聊天、上论坛不是吗,传个文件不压缩的话肯定死慢死慢的,所以压缩在那个时代很重要。当时有个商业公司提供了一种称为ARC的压缩软件,可以让你在那个时代聊天更快,当然是要付费的,Phil Katz就感觉到不爽,于是写了一个PKARC,免费的,看名字知道是兼容ARC的,于是网友都用PKARC了,ARC那个公司自然就不爽,把哥们告上了法庭,说牵涉了知识产权等等,结果Phil Katz坐牢了。。。牛人就是牛人, 在牢里面冥思苦想,决定整一个超越ARC的牛逼算法出来,牢里面就是适合思考,用了两周就整出来的,称为PKZIP,不仅免费,而且这次还开源了,直接公布源代码,因为算法都不一样了,也就不涉及到知识产权了,于是ZIP流行开来,不过Phil Katz这个人没有从里面赚到一分钱,还是穷困潦倒,因为喝酒过多等众多原因,2000年的时候死在一个汽车旅馆里。英雄逝去,精神永存,现在我们用UE打开ZIP文件,我们能看到开头的两个字节就是PK两个字符的ASCII码。
压缩案例
和 tar 的过程很像,只有些小的差别。
package main
import (
"archive/zip"
"fmt"
"io"
"log"
"os"
"path/filepath"
"strings"
)
func main() {
var src = "log"
var dst = "log.zip"
if err := Zip(dst, src); err != nil {
log.Fatalln(err)
}
}
func Zip(dst, src string) (err error) {
fw, err := os.Create(dst)
defer fw.Close()
if err != nil {
return err
}
zw := zip.NewWriter(fw)
defer zw.Close()
return filepath.Walk(src, func(path string, fi os.FileInfo, errBack error) (err error) {
if errBack != nil {
return errBack
}
fh, err := zip.FileInfoHeader(fi)
if err != nil {
return
}
fh.Name = strings.TrimPrefix(path, string(filepath.Separator)) // 重要步骤
if fi.IsDir() {
fh.Name += "/" //重要步骤 有小坑
}
w, err := zw.CreateHeader(fh)
if err != nil {
return
}
if !fh.Mode().IsRegular() { //常规文件才继续写入
return nil
}
fr, err := os.Open(path)
defer fr.Close()
if err != nil {
return
}
n, err := io.Copy(w, fr) //内存拷贝
if err != nil {
return
}
return nil
})
}
解压缩案例
package main
import (
"archive/zip"
"fmt"
"io"
"log"
"os"
"path/filepath"
)
func main() {
var src = "log.zip"
var dst = "./"
if err := UnZip(dst, src); err != nil {
log.Fatalln(err)
}
}
func UnZip(dst, src string) (err error) {
zr, err := zip.OpenReader(src)
defer zr.Close()
if err != nil {
return
}
_, err := os.Stat(dst) //判断目录是否存在
if err != nil {
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
}
fr, err := file.Open()
defer fr.Close()
if err != nil {
return err
}
fw, err := os.OpenFile(path, os.O_CREATE|os.O_RDWR|os.O_TRUNC, file.Mode())
defer fw.Close()
if err != nil {
return err
}
n, err := io.Copy(fw, fr)
if err != nil {
return err
}
}
return nil
}
常见坑
使用我们第一个Zip函数压缩的文件,如果传入的是文件而不是文件夹,例如window下 srcFile是 C:\src\1.zip。 这样用Unzip函数无法解压,比如destDir路径设置为E:\UnzipFile
header.Name += “/” 这句代码直接使用 / 不太好
解决办法
//header.Name = strings.TrimPrefix(path, filepath.Dir(srcFile) + "/") //原来
header.Name = strings.TrimPrefix(path, srcFile + string(os.PathSeparator)) //修复
不包含目录文件夹的情况下将文件压缩为 .zip?
场景 dst 不带目录直接是一个文件例如 bak.zip
解决办法
只需在 zip header 中使用文件的基本名称即可。
header.Name = filepath.Base(filename)