目录
压缩和解压文件(夹)
本代码实现了单个文件压缩和解压缩,多个文件(夹)压缩和解压缩。压缩文件最后修改时间,以原文件的时间为准。
少废话直接上代码,本地测试过。
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())
}