zoukankan      html  css  js  c++  java
  • 使用Golang解压缩文件遇到的问题及解决方法

    问题描述

    最近做广告业务获取某推的广告成效,与其他渠道不同的是,最终拿到的成效数据是一个压缩包的HTTP流数据。

    将数据写入到本地生成了一个以.gz为后缀的压缩包文件,解压以后的文件存放着json格式的成效数据。

    当然需要程序去解压缩这个压缩包获取里面的文件了。

    内置tar包的问题

    参考网上大佬们之前的解决方案写了一段测试代码:

    // TODO:解压gz文件
    func decompressionGZ(fileName string) error {
        filePath := GZIPS_PATH + fileName
        // file read
        fr, err := os.Open(filePath)
        if err != nil {
            return err
        }
        fmt.Println("fr>>> ", fr)
        defer fr.Close()
        // gzip read
        // TODO:这一步会校验文件的Header
        gr, err := gzip.NewReader(fr)
    
        fmt.Println("gr_before>>> ", gr)
    
        //createTime := time.Date(2020, 01, 01, 00, 00, 00, 00, TIME_LOCATION_CST)
        //gr.Header.Comment = "xxx"
        //gr.Header.Name = "whw"
        //gr.Header.ModTime = createTime
        
        //fmt.Println("gr.after>>>>> ", gr)
    
        fmt.Println("gr.Header>>>>> ", gr.Header)
        fmt.Println("gr.err>>>>>> ", err)
        if err != nil {
            return err
        }
        defer gr.Close()
        // tar read
        tr := tar.NewReader(gr)
        //fmt.Println("tr>>> ", tr)
        // 读取文件
        for {
            h, err := tr.Next()
            fmt.Println("h_err>>>>>> ", err)
            fmt.Println("h>>> ", h)
            if err == io.EOF {
                break
            }
            if err != nil {
                fmt.Println("err0>>> ", err)
                return err
            }
            // 打开文件
            fw, err := os.OpenFile("xxx.json", os.O_CREATE|os.O_WRONLY, os.ModePerm)
            if err != nil {
                fmt.Println("err1...... ", err)
                return err
            }
            defer fw.Close()
            // 写文件
            _, err = io.Copy(fw, tr)
            if err != nil {
                fmt.Println("err2>>> ", err)
                return err
            }
        }
        return nil
    }
    使用原生golang包tar解压的函数

    运行完这段代码一直会报一个错:archive/tar: invalid tar header

    就是说,在tar模块进行校验的时候检测到了一个“非法”的tar header!(注意我们得到的文件的后缀名是xxx.json.gz)

    研究了一下具体的实现过程,其实使用golang原生的tar包解压缩文件的话都会对header做一下校验!

    至于原因,我们可以看一下使用原生golang实现压缩文件的过程:

    func compressionGZ(fileName string) error {
        // 创建文件
        fw, err := os.Create(fileName)
        if err != nil {
            fmt.Println("werr1>>> ", err)
            return err
        }
        defer fw.Close()
        // gzip write
        gw := gzip.NewWriter(fw)
        defer gw.Close()
        // tar write
        tw := tar.NewWriter(gw)
        defer tw.Close()
    
        // 打开文件夹
        dir, err := os.Open(GZIPS_PATH)
        if err != nil {
            fmt.Println("打开文件夹错误>>> ", err)
            return err
        }
        defer dir.Close()
        // 读取文件列表
        fis, err := dir.Readdir(0)
        if err != nil {
            fmt.Println("读取文件列表错误>>> ", err)
            return err
        }
        // 遍历文件列表
        for _, fi := range fis {
            // TODO:遇到文件夹先不管,不递归了
            if fi.IsDir() {
                continue
            }
            // 开始写入数据
            fr, err := os.Open(dir.Name() + "/" + fi.Name())
            if err != nil {
                fmt.Println("werr2>>> ", err)
                return err
            }
            defer fr.Close()
    
            // 设置信息头
            h := new(tar.Header)
            // TODO:压缩的时候设置Header!!!
            // TODO:注意这里的名称需要去掉后面的 .gz
            h.Name = fileName[:len(fileName)-3]
            h.Mode = int64(fi.Mode())
            h.Size = fi.Size()
            // 写信息头
            err = tw.WriteHeader(h)
            if err != nil {
                fmt.Println("werr3>>> ", err)
                return err
            }
    
            // 写文件
            _, err = io.Copy(tw, fr)
            if err != nil {
                fmt.Println("werr4>>> ", err)
                return err
            }
        }
        return nil
    }
    使用原生golang包tar压缩的函数

    里面有一段代码需要注意:

    // 设置信息头
    h := new(tar.Header)
    // TODO:压缩的时候设置Header!!!
    // TODO:注意这里的名称需要去掉后面的 .gz
    h.Name = fileName[:len(fileName)-3]
    h.Mode = int64(fi.Mode())
    h.Size = fi.Size()
    // 写信息头
    err = tw.WriteHeader(h)

    在golang的tar模块进行压缩文件的时候需要设置一下Header——所以在解压的时候才会校验tar的Header!

    而且需要注意:正常情况下我们得到的压缩文件的后缀是xxx.tar.gz,但是,twitter渠道给的文件的后缀名是xxx.json.gz,上面代码在解压的过程中是需要校验一下tar包的header的!我们现在得到的文件当然是没有tar包的header的!所以会报错!!!

    解决方案

    在网上找了下,有一个第三方包可以顺利解决压缩与解压的问题:https://github.com/c4milo/unpackit

    下面是我的测试:

    package pgzip
    
    import (
        "fmt"
        "github.com/c4milo/unpackit"
        "os"
        "testing"
    )
    
    var filename = "SDJ9NgtdiyZwKaR9eEJQ7vOQm1UXJXWmeAmbZ5XmdBJ5Adj6gXadqEGXMPZNQO2H61cJXkcjMGJcQm6bWGyNB-9SZAId0SL9vVMgdoU5M8w3d6yXALPIrtxFTx5Whf3S.json.gz"
    
    func TestUnpackIt(t *testing.T){
    
        filePath := GZIPS_PATH + filename
        file, err1 := os.Open(filePath)
        if err1 != nil{
            fmt.Println("err1>>> ", err1)
        }
        destPath, err := unpackit.Unpack(file,GZIPS_PATH)
        if err != nil{
            fmt.Println("err>>> ", err)
        }else{
            fmt.Println("destPath>>> ", destPath)
        }
    
    }
    unpackit包的测试

    当然,这个包还可以unpack HttpRedponse,readme文件有具体的例子,带入自己的代码就好了。

    简单原理

    简单看了下里面的源码,它使用的是golang另外一个内置包bufio包实现的,有时间大家可以研究一下。

    并发情况下存在的问题

    当然这个包虽然解决了我们上面提到的解压的问题,但是有一个小小的问题,就是返回的文件名是固定的,在并发的场景中多个gorountine操作同一个文件十分危险,当然我们可以给多个gorountine之间加互斥锁保证并发的安全,也可以在不同的gorountine中生成一个唯一的解压缩后的文件名保证goruntine之间不能操作到同一个文件即可。

  • 相关阅读:
    Python程序中的线程操作-concurrent模块
    python程序中的进程操作-进程间的数据共享
    有几个消费者就需要发送几次结束信号
    python进程池
    Python程序中的线程操作-concurrent模块
    Python程序中的线程操作-线程队列
    Python程序中的线程操作-守护线程
    进程操作-进程池
    进程池版socket并发聊天
    使用多进程请求多个url来减少网络等待浪费的时间
  • 原文地址:https://www.cnblogs.com/paulwhw/p/14336742.html
Copyright © 2011-2022 走看看