archive/tar 实现打包压缩及解压

主要功能

这个包比较简单,就是将文件或目录进行打包和解包。因为很多压缩算法不支持目录压缩,所以我们一般先使用tar包把目录打包成一个文件,在使用其他压缩算进一步处理。 例如gzip

使用场景

数据备份,例如日志啊,一些文字资源,定时备份到远程,可以先tar打包,再压缩,减小体积,从而节约成本

image

(说明:图片来自维基百科)

单个文件操作

package main

import (
    "os"
    "log"
    "archive/tar"
    "fmt"
    "io"
)

func main() {
    // 准备打包的源文件
    var srcFile = "wike"
    // 打包后的文件
    var desFile = fmt.Sprintf("%s.tar",srcFile)

  
    fw, err := os.Create(desFile) //创建目标文件
    log.Println(err)
    defer fw.Close()
    
    tw := tar.NewWriter(fw)   // 通过 fw 创建一个 tar.Writer
   
    defer tw.Close()  // 这里不要忘记关闭,如果不能成功关闭会造成 tar 包不完整

    
    fi, err := os.Stat(srcFile)  //获取文件描述句柄   
    log.Println(err)
    hdr, err := tar.FileInfoHeader(fi, "")  //通过文件句柄生成文件头信息

    err = tw.WriteHeader(hdr) // 将 tar 的文件头信息 hdr 写入到 tw
    log.Println(err)

  
    fr, err := os.Open(srcFile)   // 打开准备写入的文件
    log.Println(err)
    defer fr.Close()

    _, err := io.Copy(tw, fr) //全量数据拷贝,大文件容易出现问题,可以通过其他手段优化
    log.Println(err)
}

单个文件解包


package main

import (
    "os"
    "archive/tar"
    "io"
    "log"
)

func main() {

    var srcFile = "wike.tar"

  
    fr, err := os.Open(srcFile)  // 将 tar 包打开
    
    log.Println(err)
    
    defer fr.Close()

    tr := tar.NewReader(fr) // 通过文件句柄fr,创建一个可读流
 
    for hdr, err := tr.Next(); err != io.EOF; hdr, err = tr.Next(){
        if err!=nil{
          log.Println(err) //处理错误,一般不会发生
        }
    
   
        fi := hdr.FileInfo()      // 获取文件信息

       
        fw, err := os.Create(fi.Name())  // 创建一个空文件,用来写入解包后的数据
        defer  fw.Close()
        log.Println(err)

      
        n, err := io.Copy(fw, tr) //全量数据拷贝,大文件容易出现问题,可以通过其他手段优化
        log.Println(err)
       
    }
}

目录打包(重要)

package main

import (
    "archive/tar"
    "compress/gzip"
    "fmt"
    "io"
    "log"
    "os"
    "path/filepath"
    "strings"
)

func main() {


    var src = "apt"
    var dst = fmt.Sprintf("%s.tar", src)

    // 将步骤写入了一个函数中,这样处理错误方便一些
    Tar(src, dst); 
}

func Tar(src, dst string)  {
    // 创建文件
    fw, err := os.Create(dst)
    if err != nil {
        return
    }
    defer fw.Close()
    tw := tar.NewWriter(gw)
    defer tw.Close()
    
    s, err := os.Stat(src) 
    
    if !s.IsDir(){
        return //不是目录不处理
    } 
    
    
    //filepath.Walk这个函数很重要遍历目录文件

    filepath.Walk(src, func(fileName string, fi os.FileInfo, err error) error {
    
        if err != nil { 
            return err     // 处理错误一般不会出错
        }

     
        hdr, err := tar.FileInfoHeader(fi, "")
        if err != nil {
            return err
        }

        hdr.Name = strings.TrimPrefix(fileName, string(filepath.Separator))

  
        if err := tw.WriteHeader(hdr); err != nil {
            return err
        }

       
        if !fi.Mode().IsRegular() {
            return nil
        }

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

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


        return nil
    })
}

目录解包解压


func UnTar(dst, src string)  {
  
    fr, err := os.Open(src)  
    if err != nil {
        return
    }
    defer fr.Close()

    tr := tar.NewReader(fr)

 
    for {
        hdr, err := tr.Next()

        switch {
        case err == io.EOF:
            return nil
        case err != nil:
        
            return err
            
        case hdr == nil:
            
            continue
        }

    
        dstFileDir := filepath.Join(dst, hdr.Name)

        switch hdr.Typeflag {
        
            case tar.TypeDir: 
                if b := ExistDir(dstFileDir); !b {
                       
                    if err := os.MkdirAll(dstFileDir, 0775); err != nil {
                        return err
                    }
                }
            case tar.TypeReg: 
             
                file, err := os.OpenFile(dstFileDir, os.O_CREATE|os.O_RDWR, os.FileMode(hdr.Mode))
                defer file.Close()
                if err != nil {
                    return err
                }
                _, err := io.Copy(file, tr)
                if err != nil {
                    return err
                }
            }
    }

}

实现流程

打包和解包的原理和实现

1、打包实现原理

先创建一个文件xxx.tar,然后向xxx.tar写入tar头部信息。打开要被tar的文件,向xxx.tar写入头部信息,然后向xxx.tar写入文件信息。重复第二步直到所有文件都被写入到x.tar中,关闭x.tar,整个过程就这样完成了

2、解包实现原理

先打开tar文件,然后从这个tar头部中循环读取存储在这个归档文件内的文件头信息,从这个文件头里读取文件名,以这个文件名创建文件,然后向这个文件里写入数据