使用unpackit包解压gz包遇到的一个问题与解决方案
问题描述
上一篇博客介绍了使用go解决文件压缩问题:使用Golang解压缩文件遇到的问题及解决方法
文中用到了unpackit这个第三方包。
但是,实际中我在解压文件的时候遇到了这样的情况:在电脑手动解压的文件大概有120多k,文件是完整的,但是使用代码解压之后的文件竟然只有4k!
毫无疑问:文件中的数据也比完整的数据少了很多!
源码浅析
研究了一下unpackit这个包的源码,终于找到了问题所在!
我们可以在源码中的unpackit.go文件中找到下面这段代码:
switch ftype { case "gzip": gzr, err := gzip.NewReader(r) if err != nil { return "", err } defer func() { if err := gzr.Close(); err != nil { fmt.Printf("%+v", errors.Wrapf(err, "unpackit: failed closing gzip reader")) } }() decompressingReader = bufio.NewReader(gzr)
其实问题就是出在了bufio.NewReader方法上了!
打开bufio.NewReader的源码,可以找到,官方默认只给缓冲区设置了4k的大小!
const ( defaultBufSize = 4096 ) // NewReader returns a new Reader whose buffer has the default size. func NewReader(rd io.Reader) *Reader { return NewReaderSize(rd, defaultBufSize) }
所以当我们解压之后的json文件超过了4k的话,程序会默认将后面的数据省略掉!
问题解决
既然找到了问题的原因所在,可以改造一下官方的源码,当我们获取到的文件不是一个xxx.tar.gz文件而是一个直接由源文件打包成的gz文件的话必须得设置一下缓冲区的大小!
改造后的Unpack函数如下:
func Unpack(reader io.Reader, destPath string, unTarName string, unTarGzSize int) (string, error) { var err error if destPath == "" { destPath, err = ioutil.TempDir(os.TempDir(), "unpackit-") if err != nil { return "", err } } // Makes sure destPath exists if err := os.MkdirAll(destPath, os.ModePerm); err != nil { return "", err } r := bufio.NewReader(reader) // Reads magic number from the stream so we can better determine how to proceed ftype, err := magicNumber(r, 0) if err != nil { return "", err } var decompressingReader *bufio.Reader switch ftype { case "gzip": gzr, err := gzip.NewReader(r) if err != nil { return "", err } defer func() { if err := gzr.Close(); err != nil { fmt.Printf("%+v", errors.Wrapf(err, "unpackit: failed closing gzip reader")) } }() // TODO:注意这里相当于:bufio.NewReaderSize(gzr, 4096),如果不是tar包并且文件大小超出了4k,会丢失数据 // TODO:需要加大一下缓冲区的大小! // decompressingReader = bufio.NewReader(gzr) decompressingReader = bufio.NewReaderSize(gzr, unTarGzSize) case "xz": xzr, err := xz.NewReader(r) if err != nil { return "", err } decompressingReader = bufio.NewReader(xzr) case "bzip": br, err := bzip2.NewReader(r, nil) if err != nil { return "", err } defer func() { if err := br.Close(); err != nil { fmt.Printf("%+v", errors.Wrapf(err, "unpackit: failed closing bzip2 reader")) } }() decompressingReader = bufio.NewReader(br) case "zip": // Like TAR, ZIP is also an archiving format, therefore we can just return // after it finishes return Unzip(r, destPath) default: // maybe it is a tarball file fmt.Println("进入了default!!!!! ") decompressingReader = r } // Check magic number in offset 257 too see if this is also a TAR file ftype, err = magicNumber(decompressingReader, 257) if err != nil { return "", err } if ftype == "tar" { return Untar(decompressingReader, destPath) } // If it's not a TAR archive then save it to disk as is. destRawFile := filepath.Join(destPath, sanitize(path.Base(unTarName))) // Creates destination file destFile, err := os.Create(destRawFile) if err != nil { return "", err } defer func() { if err := destFile.Close(); err != nil { log.Println(err) } }() // Copies data to destination file if _, err := io.Copy(destFile, decompressingReader); err != nil { return "", err } return destPath, nil }
改造后的项目
改造之后的项目放在了github上,欢迎star:https://github.com/Wanghongw/unpackit