使用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
}
View Code

改造后的项目

改造之后的项目放在了github上,欢迎star:https://github.com/Wanghongw/unpackit

 

posted on 2021-01-28 20:48  江湖乄夜雨  阅读(394)  评论(0编辑  收藏  举报