golang 实现本地 gzip 文件归档和解压

目录

压缩和解压文件(夹)

解压和压缩字符串


压缩和解压文件(夹)

本代码实现了单个文件压缩和解压缩,多个文件(夹)压缩和解压缩。压缩文件最后修改时间,以原文件的时间为准。

少废话直接上代码,本地测试过。

package compressor

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

// CompressSingleFile 压缩单个文件
//
// srcName: 被压缩的文件
//
// tarName: 压缩包的文件名。缺省值为{srcName}+".tar.gz"
func CompressSingleFile(srcName, tarName string) error {
	if srcName == "" {
		return errors.New("source file name can not be empty")
	}
	if tarName == "" {
		tarName = srcName + ".tar.gz"
	}
	from, err := os.Open(srcName)
	defer from.Close()
	if err != nil {
		return fmt.Errorf("failed to open file: %v", err)
	}
	return CompressOpenedSingleFile(from, tarName)
}

// CompressOpenedSingleFile 压缩一个被打开的文件
//
// from: 需要压缩的的文件的句柄
//
// tarName: 压缩包的文件名。缺省值为{from.Name()}+".tar.gz"
func CompressOpenedSingleFile(from *os.File, tarName string) error {
	if tarName == "" {
		tarName = from.Name() + ".tar.gz"
	}
	info, err := from.Stat()
	if err != nil {
		return fmt.Errorf("failed to read file information: %v", err)
	}
	if info.IsDir() {
		return fmt.Errorf("%v is a Director but not file: %v", from.Name(), err)
	}
	fi, err := from.Stat()
	if err != nil {
		return fmt.Errorf("failed to stat file: %v", err)
	}
	gzf, err := os.OpenFile(tarName, os.O_CREATE|os.O_TRUNC|os.O_RDWR, fi.Mode())
	if err != nil {
		return fmt.Errorf("failed to open compressed log file: %v", err)
	}
	gz := gzip.NewWriter(gzf)
	gz.Name = info.Name()
	gz.ModTime = info.ModTime()
	_, err = io.Copy(gz, from)
	if err != nil {
		return fmt.Errorf("failed to compress source file: %v", err)
	}
	if err := gz.Close(); err != nil {
		fmt.Println(err)
	}
	if err := gzf.Close(); err != nil {
		fmt.Println(err)
	}
	return nil
}

// DecompressSingleFile 解压缩单个文件,
// 文件名以压缩文件内部名称为准。如果出现文件名冲突,会加入 .new 中缀.
// 例如已经存在 a.txt 解压 出来的 a.txt 会更名为 a.new.txt
func DecompressSingleFile(tarName string) (string, error) {
	if tarName == "" {
		return "", errors.New("target file name can not be empty")
	}
	tarFile, err := os.Open(tarName)
	defer tarFile.Close()
	if err != nil {
		return "", fmt.Errorf("failed to open tar file: %v", err)
	}
	gr, err := gzip.NewReader(tarFile)
	if err != nil {
		return "", fmt.Errorf("failed to read tar file: %v", err)
	}
	dir := path.Dir(tarName)
	fileName := sureFileName(dir, gr.Name)
	fi, err := tarFile.Stat()
	if err != nil {
		return "", fmt.Errorf("failed to read tar file info: %v", err)
	}
	w, err := os.OpenFile(fileName, os.O_CREATE|os.O_TRUNC|os.O_RDWR, fi.Mode())
	if err != nil {
		return "", fmt.Errorf("failed to create decompress file: %v", err)
	}
	_, err = io.Copy(w, gr)
	if err != nil {
		return "", fmt.Errorf("failed to decompress tar file: %v", err)
	}
	if err := gr.Close(); err != nil {
		return fileName, err
	}
	if err := w.Close(); err != nil {
		return fileName, err
	}
	atime := time.Now()
	os.Chtimes(fileName, atime, gr.ModTime)
	return fileName, nil
}

func sureFileName(dir, fileName string) string {
	filePathName := filepath.Join(dir, fileName)
	_, err := os.Lstat(filePathName)
	if err != nil {
		return filePathName
	}
	fileSuffix := path.Ext(fileName)
	pureName := strings.TrimSuffix(fileName, fileSuffix)
	fileName = pureName + ".new" + fileSuffix
	return sureFileName(dir, fileName)
}

// 以下代码是打包压缩多个文件和文件夹的

type dirInfo struct {
	Name    string
	ModTime time.Time
}

// CompressFilesOrFolds 压缩文件和目录
//
// paths: 要压缩的路径
//
// dest: 压缩文件名称。默认是第一个路径加后缀 .tar.gz
func CompressFilesOrFolds(paths []string, dest string) (err error) {
	if len(paths) < 1 {
		return errors.New("No files to compress")
	}
	if dest == "" {
		dest = paths[0] + ".tar.gz"
	}
	files := make([]*os.File, len(paths))
	defer func() {
		//因为需要等压缩完成才关闭文件句柄,因此希望不要一次压缩太多文件
        for _, f := range files {
			if f != nil {
				f.Close()
			}
		}
	}()
	for i, name := range paths {
		files[i], err = os.Open(name)
		if err != nil {
			return err
		}
	}
	return Compress(files, dest)
}

// Compress 压缩 使用gzip压缩成 tar.gz
func Compress(files []*os.File, dest string) error {
	d, _ := os.Create(dest)
	defer d.Close()
	gw := gzip.NewWriter(d)
	defer gw.Close()
	tw := tar.NewWriter(gw)
	defer tw.Close()
	for _, file := range files {
		err := compress(file, "", tw)
		if err != nil {
			return err
		}
	}
	return nil
}

func compress(file *os.File, prefix string, tw *tar.Writer) error {
	info, err := file.Stat()
	if err != nil {
		return err
	}
	if info.IsDir() {
		prefix = filepath.Join(prefix, info.Name())
		fileInfos, err := file.Readdir(-1)
		if err != nil {
			return err
		}
		for _, fi := range fileInfos {
			f, err := os.Open(filepath.Join(file.Name(), fi.Name()))
			if err != nil {
				return err
			}
			err = compress(f, prefix, tw)
			if err != nil {
				return err
			}
		}
	} else {
		header, err := tar.FileInfoHeader(info, "")
		header.Name = filepath.Join(prefix, header.Name)
		if err != nil {
			return err
		}
		err = tw.WriteHeader(header)
		if err != nil {
			return err
		}
		_, err = io.Copy(tw, file)
		file.Close()
		if err != nil {
			return err
		}
	}
	return nil
}

// Decompress 解压 tar.gz。 保留原始的层级结构和文件修改时间
//
// tarFile 被解压的 .tar.gz文件名
//
// dest 解压到哪个目录,结尾的"/"可有可无。"" 和 "./" 和 "." 都表示解压到当前目录。
func Decompress(tarFile, dest string) error {
	srcFile, err := os.Open(tarFile)
	if err != nil {
		return err
	}
	defer srcFile.Close()
	gr, err := gzip.NewReader(srcFile)
	if err != nil {
		return err
	}
	defer gr.Close()
	tr := tar.NewReader(gr)
	if dest != "" {
		_, err = makeDir(dest)
		if err != nil {
			return err
		}
	}
	currentDir := dirInfo{}
	for {
		header, err := tr.Next()
		if err != nil {
			if err == io.EOF {
				if currentDir.Name != "" {
					remodifyTime(currentDir.Name, currentDir.ModTime)
				}
				break
			} else {
				return err
			}
		}
		fi := header.FileInfo()
		fileName := filepath.Join(dest, header.Name)
		if !strings.HasPrefix(fileName, currentDir.Name) {
			remodifyTime(currentDir.Name, currentDir.ModTime)
		}
		if fi.IsDir() {
			foldName, err := makeDir(fileName)
			if err != nil {
				return err
			}
			currentDir = dirInfo{
				foldName,
				fi.ModTime(),
			}
			continue
		}
		file, err := createFile(fileName)
		if err != nil {
			return fmt.Errorf("can not create file %v: %v", fileName, err)
		}
		io.Copy(file, tr)
		file.Close()
		remodifyTime(fileName, header.ModTime)
	}
	return nil
}

func remodifyTime(name string, modTime time.Time) {
	if name == "" {
		return
	}
	atime := time.Now()
	os.Chtimes(name, atime, modTime)
}

func makeDir(name string) (string, error) {
	if name != "" {
		_, err := os.Stat(name)
		if err != nil {
			err = os.MkdirAll(name, 0755)
			if err != nil {
				return "", fmt.Errorf("can not make directory: %v", err)
			}
			return name, nil
		}
		return "", nil
	}
	return "", fmt.Errorf("can not make no name directory: %v", name)
}

func createFile(name string) (*os.File, error) {
	dir := path.Dir(name)
	if dir != "" {
		_, err := os.Lstat(dir)
		if err != nil {
			err := os.MkdirAll(dir, 0755)
			if err != nil {
				return nil, err
			}
		}
	}
	return os.Create(name)
}

解压和压缩字符串

缺点是字符串不能大到上G,因为,都在内存里面,比较占内存空间。

func CompressString(source string) string {
	var cpBuf []byte
	buf := bytes.NewBuffer(cpBuf)
	writer, _ := gzip.NewWriterLevel(buf, gzip.BestCompression)
	stringReader := bytes.NewReader([]byte(source))
	_, _ = io.Copy(writer, stringReader)
	_ = writer.Close()
	return string(buf.Bytes())
}

func DecompressString(source string) string {
	reader := bytes.NewReader([]byte(source))
	gr, _ := gzip.NewReader(reader)
	buf := bytes.NewBuffer([]byte{})
	_, _ = io.Copy(buf,gr)
	_ = gr.Close()
	return string(buf.Bytes())
}

评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值