Keyboard shortcuts

Press or to navigate between chapters

Press S or / to search in the book

Press ? to show this help

Press Esc to hide this help

Go 语言标准库 —— compress/lzw 包(LZW 压缩/解压缩)


🔹 概述

compress/lzw 包实现了 LZW(Lempel-Ziv-Welch)压缩算法。

主要功能:

  • LZW 格式压缩
  • LZW 格式解压缩
  • 支持 LSB 和 MSB 两种位序
  • 流式处理

重要说明:

  • LZW 是无损压缩算法
  • 主要用于 GIF 图像格式
  • 也用于 Unix compress 命令
  • 压缩速度较快,但压缩率一般

位序(Order):

  • LSB (Least Significant Bit) - 最低有效位优先
  • MSB (Most Significant Bit) - 最高有效位优先

应用场景:

  • GIF 图像压缩(使用 LSB)
  • TIFF 图像格式
  • Unix compress 工具

🔹 核心函数

创建 LZW 写入器(压缩)

lzw.NewWriter(w io.Writer, order lzw.Order, litWidth int) io.WriteCloser

  • 说明:

    • 创建 LZW 压缩写入器
    • 将写入的数据进行 LZW 压缩
    • 流式处理
  • 参数:

    • w io.Writer - 底层写入器
    • order lzw.Order - 位序(LSB 或 MSB)
    • litWidth int - 字面量宽度(通常为 8)
  • 返回值:

    • io.WriteCloser - 实现了 Write 和 Close 方法的写入器
  • 注意:

    • litWidth 通常为 8
    • 必须调用 Close() 完成压缩
    • 不支持压缩级别调节
  • 示例:

    // 创建 LZW 写入器(LSB 位序,8 位字面量宽度)
    writer := lzw.NewWriter(file, lzw.LSB, 8)
    defer writer.Close()
    
    writer.Write([]byte("hello world"))
    

创建 LZW 读取器(解压缩)

lzw.NewReader(r io.Reader, order lzw.Order, litWidth int) io.ReadCloser

  • 说明:

    • 创建 LZW 解压缩读取器
    • 从底层读取器读取压缩数据并解压缩
    • 流式处理
  • 参数:

    • r io.Reader - 底层读取器
    • order lzw.Order - 位序(LSB 或 MSB)
    • litWidth int - 字面量宽度(通常为 8)
  • 返回值:

    • io.ReadCloser - 实现了 Read 和 Close 方法的读取器
  • 注意:

    • litWidth 通常为 8
    • 使用完后必须调用 Close()
  • 示例:

    // 创建 LZW 读取器
    reader := lzw.NewReader(file, lzw.LSB, 8)
    defer reader.Close()
    
    data, err := io.ReadAll(reader)
    

🔹 位序类型

Order 类型

lzw.Order type

type Order int

const (
    LSB Order = iota // 最低有效位优先
    MSB              // 最高有效位优先
)

LSB(Least Significant Bit):

  • 最低有效位优先
  • GIF 图像格式使用
  • 位读取顺序:从右到左

MSB(Most Significant Bit):

  • 最高有效位优先
  • Unix compress 使用
  • 位读取顺序:从左到右

选择建议:

  • GIF 图像 👉 使用 LSB
  • Unix compress 👉 使用 MSB
  • 一般用途 👉 推荐使用 LSB

🔹 使用场景

1. 基础压缩和解压缩

package main

import (
    "bytes"
    "compress/lzw"
    "fmt"
    "io"
)

func main() {
    // 原始数据
    original := []byte("Hello, World! This is a test of LZW compression.")
    
    // 压缩
    var compressed bytes.Buffer
    writer := lzw.NewWriter(&compressed, lzw.LSB, 8)
    writer.Write(original)
    writer.Close()
    
    // 解压缩
    reader := lzw.NewReader(&compressed, lzw.LSB, 8)
    decompressed, _ := io.ReadAll(reader)
    reader.Close()
    
    // 验证
    fmt.Printf("原始大小:%d 字节\n", len(original))
    fmt.Printf("压缩大小:%d 字节\n", compressed.Len())
    fmt.Printf("解压大小:%d 字节\n", len(decompressed))
    fmt.Printf("数据一致:%v\n", bytes.Equal(original, decompressed))
}

2. 文件压缩和解压缩

package main

import (
    "compress/lzw"
    "fmt"
    "io"
    "os"
)

func compressFile(src, dst string, order lzw.Order) error {
    // 打开源文件
    srcFile, err := os.Open(src)
    if err != nil {
        return fmt.Errorf("打开源文件:%w", err)
    }
    defer srcFile.Close()
    
    // 创建目标文件
    dstFile, err := os.Create(dst)
    if err != nil {
        return fmt.Errorf("创建目标文件:%w", err)
    }
    defer dstFile.Close()
    
    // 创建 LZW 写入器
    writer := lzw.NewWriter(dstFile, order, 8)
    defer writer.Close()
    
    // 复制并压缩
    _, err = io.Copy(writer, srcFile)
    if err != nil {
        return fmt.Errorf("压缩失败:%w", err)
    }
    
    // 显示压缩效果
    srcInfo, _ := os.Stat(src)
    dstInfo, _ := os.Stat(dst)
    ratio := float64(dstInfo.Size()) / float64(srcInfo.Size()) * 100
    
    fmt.Printf("压缩完成:%s -> %s\n", src, dst)
    fmt.Printf("压缩率:%.2f%%\n", ratio)
    
    return nil
}

func decompressFile(src, dst string, order lzw.Order) error {
    // 打开源文件
    srcFile, err := os.Open(src)
    if err != nil {
        return fmt.Errorf("打开源文件:%w", err)
    }
    defer srcFile.Close()
    
    // 创建目标文件
    dstFile, err := os.Create(dst)
    if err != nil {
        return fmt.Errorf("创建目标文件:%w", err)
    }
    defer dstFile.Close()
    
    // 创建 LZW 读取器
    reader := lzw.NewReader(srcFile, order, 8)
    defer reader.Close()
    
    // 复制并解压
    _, err = io.Copy(dstFile, reader)
    if err != nil {
        return fmt.Errorf("解压失败:%w", err)
    }
    
    fmt.Printf("解压完成:%s -> %s\n", src, dst)
    return nil
}

func main() {
    // 压缩(使用 LSB 位序)
    err := compressFile("input.txt", "input.txt.lzw", lzw.LSB)
    if err != nil {
        fmt.Println("压缩失败:", err)
        return
    }
    
    // 解压
    err = decompressFile("input.txt.lzw", "output.txt", lzw.LSB)
    if err != nil {
        fmt.Println("解压失败:", err)
        return
    }
}

3. GIF 图像处理(LSB 位序)

package main

import (
    "bytes"
    "compress/lzw"
    "fmt"
    "image"
    "image/gif"
    "io"
    "os"
)

// 压缩 GIF 图像数据
func compressGIFData(data []byte) ([]byte, error) {
    var buf bytes.Buffer
    writer := lzw.NewWriter(&buf, lzw.LSB, 8)
    
    _, err := writer.Write(data)
    if err != nil {
        writer.Close()
        return nil, err
    }
    
    err = writer.Close()
    if err != nil {
        return nil, err
    }
    
    return buf.Bytes(), nil
}

// 解压缩 GIF 图像数据
func decompressGIFData(data []byte) ([]byte, error) {
    reader := lzw.NewReader(bytes.NewReader(data), lzw.LSB, 8)
    defer reader.Close()
    
    return io.ReadAll(reader)
}

// 读取 GIF 文件
func readGIF(filename string) (*gif.GIF, error) {
    file, err := os.Open(filename)
    if err != nil {
        return nil, err
    }
    defer file.Close()
    
    return gif.DecodeAll(file)
}

func main() {
    // 读取 GIF
    g, err := readGIF("image.gif")
    if err != nil {
        fmt.Println("读取 GIF 失败:", err)
        return
    }
    
    fmt.Printf("GIF 图像:%dx%d, %d 帧\n", 
        g.Config.Width, g.Config.Height, len(g.Image))
    
    // 处理每一帧的图像数据
    for i, img := range g.Image {
        // 压缩图像数据
        var buf bytes.Buffer
        writer := lzw.NewWriter(&buf, lzw.LSB, 8)
        
        // 写入像素数据
        for y := 0; y < img.Rect.Dy(); y++ {
            for x := 0; x < img.Rect.Dx(); x++ {
                pixel := img.Pix[y*img.Stride+x]
                writer.Write([]byte{pixel})
            }
        }
        writer.Close()
        
        fmt.Printf("帧 %d 压缩后大小:%d 字节\n", i, buf.Len())
    }
}

4. 批量压缩多个文件

package main

import (
    "compress/lzw"
    "fmt"
    "io"
    "os"
    "path/filepath"
)

func batchCompress(dir string, order lzw.Order) error {
    // 查找所有文件
    files, err := filepath.Glob(filepath.Join(dir, "*"))
    if err != nil {
        return err
    }
    
    for _, file := range files {
        info, err := os.Stat(file)
        if err != nil {
            fmt.Printf("跳过 %s: %v\n", file, err)
            continue
        }
        
        // 跳过目录和已压缩文件
        if info.IsDir() || filepath.Ext(file) == ".lzw" {
            continue
        }
        
        fmt.Printf("压缩:%s\n", file)
        
        // 压缩文件
        err = compressFile(file, file+".lzw", order)
        if err != nil {
            fmt.Printf("  失败:%v\n", err)
            continue
        }
        
        fmt.Printf("  成功\n")
    }
    
    return nil
}

func compressFile(src, dst string, order lzw.Order) error {
    srcFile, err := os.Open(src)
    if err != nil {
        return err
    }
    defer srcFile.Close()
    
    dstFile, err := os.Create(dst)
    if err != nil {
        return err
    }
    defer dstFile.Close()
    
    writer := lzw.NewWriter(dstFile, order, 8)
    defer writer.Close()
    
    _, err = io.Copy(writer, srcFile)
    return err
}

func main() {
    // 批量压缩当前目录下的所有文件
    err := batchCompress(".", lzw.LSB)
    if err != nil {
        fmt.Println("批量压缩失败:", err)
        return
    }
    fmt.Println("批量压缩完成")
}

5. 管道处理(Pipeline)

package main

import (
    "compress/lzw"
    "fmt"
    "io"
    "strings"
)

func main() {
    // 创建管道
    pr, pw := io.Pipe()
    
    // 创建 LZW 写入器
    writer := lzw.NewWriter(pw, lzw.LSB, 8)
    
    // 启动解压缩 goroutine
    go func() {
        defer pw.Close()
        
        // 写入数据
        data := "Hello, World! This is a pipeline test."
        writer.Write([]byte(data))
        writer.Close()
    }()
    
    // 解压缩
    reader := lzw.NewReader(pr, lzw.LSB, 8)
    defer reader.Close()
    
    // 读取解压后的数据
    var output strings.Builder
    io.Copy(&output, reader)
    
    fmt.Printf("解压后:%s\n", output.String())
}

6. LSB vs MSB 对比

package main

import (
    "bytes"
    "compress/lzw"
    "fmt"
    "io"
)

func compressWithOrder(data []byte, order lzw.Order) ([]byte, error) {
    var buf bytes.Buffer
    writer := lzw.NewWriter(&buf, order, 8)
    
    _, err := writer.Write(data)
    if err != nil {
        writer.Close()
        return nil, err
    }
    
    err = writer.Close()
    return buf.Bytes(), err
}

func decompressWithOrder(data []byte, order lzw.Order) ([]byte, error) {
    reader := lzw.NewReader(bytes.NewReader(data), order, 8)
    defer reader.Close()
    
    return io.ReadAll(reader)
}

func main() {
    original := []byte("Hello, World! This is a test of LZW compression with different orders.")
    
    // 使用 LSB 压缩
    lsbCompressed, _ := compressWithOrder(original, lzw.LSB)
    lsbDecompressed, _ := decompressWithOrder(lsbCompressed, lzw.LSB)
    
    // 使用 MSB 压缩
    msbCompressed, _ := compressWithOrder(original, lzw.MSB)
    msbDecompressed, _ := decompressWithOrder(msbCompressed, lzw.MSB)
    
    // 尝试错误的位序解压
    wrongDecompressed, err := decompressWithOrder(lsbCompressed, lzw.MSB)
    
    fmt.Printf("原始大小:%d 字节\n", len(original))
    fmt.Printf("LSB 压缩:%d 字节\n", len(lsbCompressed))
    fmt.Printf("MSB 压缩:%d 字节\n", len(msbCompressed))
    fmt.Printf("\n")
    fmt.Printf("LSB 解压正确:%v\n", bytes.Equal(original, lsbDecompressed))
    fmt.Printf("MSB 解压正确:%v\n", bytes.Equal(original, msbDecompressed))
    fmt.Printf("错误位序解压:error=%v, 数据=%v\n", 
        err, bytes.Equal(original, wrongDecompressed))
}

🔹 错误处理

常见错误

  • 位序不匹配

    • 说明:压缩和解压缩使用了不同的位序
    • 处理方式:确保使用相同的位序
    • 示例:
      // 压缩时使用 LSB
      writer := lzw.NewWriter(dst, lzw.LSB, 8)
      
      // 解压缩时也必须使用 LSB
      reader := lzw.NewReader(src, lzw.LSB, 8)
      
  • 未关闭写入器

    • 说明:忘记调用 Close() 导致数据不完整
    • 处理方式:始终使用 defer Close()
    • 示例:
      writer := lzw.NewWriter(dst, lzw.LSB, 8)
      defer writer.Close() // 确保关闭
      
  • 无效的压缩数据

    • 说明:数据损坏或格式错误
    • 处理方式:检查错误并验证数据
    • 示例:
      reader := lzw.NewReader(src, lzw.LSB, 8)
      data, err := io.ReadAll(reader)
      if err != nil {
          fmt.Println("无效的压缩数据:", err)
      }
      

错误处理最佳实践

package main

import (
    "compress/lzw"
    "fmt"
    "io"
    "os"
)

func safeCompress(src, dst string, order lzw.Order) (err error) {
    srcFile, err := os.Open(src)
    if err != nil {
        return fmt.Errorf("打开源文件:%w", err)
    }
    defer srcFile.Close()
    
    dstFile, err := os.Create(dst)
    if err != nil {
        return fmt.Errorf("创建目标文件:%w", err)
    }
    defer func() {
        dstFile.Close()
        if err != nil {
            os.Remove(dst)
        }
    }()
    
    writer := lzw.NewWriter(dstFile, order, 8)
    defer writer.Close()
    
    _, err = io.Copy(writer, srcFile)
    if err != nil {
        return fmt.Errorf("压缩失败:%w", err)
    }
    
    return nil
}

func safeDecompress(src, dst string, order lzw.Order) (err error) {
    srcFile, err := os.Open(src)
    if err != nil {
        return fmt.Errorf("打开源文件:%w", err)
    }
    defer srcFile.Close()
    
    dstFile, err := os.Create(dst)
    if err != nil {
        return fmt.Errorf("创建目标文件:%w", err)
    }
    defer func() {
        dstFile.Close()
        if err != nil {
            os.Remove(dst)
        }
    }()
    
    reader := lzw.NewReader(srcFile, order, 8)
    defer reader.Close()
    
    _, err = io.Copy(dstFile, reader)
    if err != nil {
        return fmt.Errorf("解压失败:%w", err)
    }
    
    return nil
}

func main() {
    // 压缩
    err := safeCompress("input.txt", "input.txt.lzw", lzw.LSB)
    if err != nil {
        fmt.Println("压缩错误:", err)
        return
    }
    
    // 解压
    err = safeDecompress("input.txt.lzw", "output.txt", lzw.LSB)
    if err != nil {
        fmt.Println("解压错误:", err)
        return
    }
    
    fmt.Println("处理完成")
}

🔹 性能优化

1. 使用缓冲 I/O

package main

import (
    "bufio"
    "compress/lzw"
    "fmt"
    "io"
    "os"
)

func compressWithBuffering(src, dst string, order lzw.Order) error {
    srcFile, err := os.Open(src)
    if err != nil {
        return err
    }
    defer srcFile.Close()
    
    dstFile, err := os.Create(dst)
    if err != nil {
        return err
    }
    defer dstFile.Close()
    
    // 使用缓冲读取
    bufReader := bufio.NewReaderSize(srcFile, 32*1024)
    
    // 使用缓冲写入
    bufWriter := bufio.NewWriterSize(dstFile, 32*1024)
    
    // 创建 LZW 写入器
    writer := lzw.NewWriter(bufWriter, order, 8)
    
    _, err = io.Copy(writer, bufReader)
    if err != nil {
        writer.Close()
        return err
    }
    
    err = writer.Close()
    if err != nil {
        return err
    }
    
    err = bufWriter.Flush()
    if err != nil {
        return err
    }
    
    fmt.Println("缓冲压缩完成")
    return nil
}

2. 对象池复用

package main

import (
    "bytes"
    "compress/lzw"
    "io"
    "sync"
)

// 注意:lzw 不支持 Reset 方法,需要手动管理
type LZWWriter struct {
    w     io.Writer
    order lzw.Order
}

func NewLZWWriter(w io.Writer, order lzw.Order) *LZWWriter {
    return &LZWWriter{w: w, order: order}
}

func (lw *LZWWriter) Write(data []byte) error {
    writer := lzw.NewWriter(lw.w, lw.order, 8)
    _, err := writer.Write(data)
    if err != nil {
        writer.Close()
        return err
    }
    return writer.Close()
}

var writerPool = sync.Pool{
    New: func() interface{} {
        return &bytes.Buffer{}
    },
}

func compress(data []byte, order lzw.Order) ([]byte, error) {
    buf := writerPool.Get().(*bytes.Buffer)
    defer writerPool.Put(buf)
    buf.Reset()
    
    writer := lzw.NewWriter(buf, order, 8)
    _, err := writer.Write(data)
    if err != nil {
        writer.Close()
        return nil, err
    }
    writer.Close()
    
    result := make([]byte, buf.Len())
    copy(result, buf.Bytes())
    
    return result, nil
}

🔹 与其他压缩算法对比

LZW vs DEFLATE vs Gzip

特性LZWDEFLATE (flate)Gzip
算法LZWDEFLATE (LZ77 + Huffman)DEFLATE + 头部
压缩率较低
压缩速度中等中等
解压速度
内存使用中等中等
主要应用GIF、TIFFZIP、PNG文件压缩、HTTP
专利状态已过期无专利无专利
压缩级别可调节可调节

选择建议

  • LZW 👉 GIF 图像处理、简单快速压缩
  • DEFLATE 👉 通用压缩、需要高压缩率
  • Gzip 👉 文件压缩、HTTP 响应压缩

🔹 实际应用示例

1. 简单的文件归档工具

package main

import (
    "compress/lzw"
    "fmt"
    "io"
    "os"
    "path/filepath"
)

type Archive struct {
    writer io.WriteCloser
}

func NewArchive(filename string) (*Archive, error) {
    file, err := os.Create(filename)
    if err != nil {
        return nil, err
    }
    
    writer := lzw.NewWriter(file, lzw.LSB, 8)
    
    return &Archive{writer}, nil
}

func (a *Archive) AddFile(filename string) error {
    data, err := os.ReadFile(filename)
    if err != nil {
        return err
    }
    
    // 写入文件名长度
    name := filepath.Base(filename)
    a.writer.Write([]byte{byte(len(name))})
    
    // 写入文件名
    a.writer.Write([]byte(name))
    
    // 写入数据长度
    a.writer.Write([]byte{
        byte(len(data) >> 24),
        byte(len(data) >> 16),
        byte(len(data) >> 8),
        byte(len(data)),
    })
    
    // 写入数据
    _, err = a.writer.Write(data)
    return err
}

func (a *Archive) Close() error {
    return a.writer.Close()
}

func main() {
    // 创建归档
    archive, _ := NewArchive("backup.lzw")
    defer archive.Close()
    
    // 添加文件
    files := []string{"file1.txt", "file2.txt", "file3.txt"}
    for _, f := range files {
        archive.AddFile(f)
        fmt.Printf("添加:%s\n", f)
    }
    
    fmt.Println("归档完成")
}

2. 压缩数据加密

package main

import (
    "bytes"
    "compress/lzw"
    "crypto/aes"
    "crypto/cipher"
    "crypto/rand"
    "fmt"
    "io"
)

// 压缩并加密
func compressAndEncrypt(data []byte, key []byte) ([]byte, error) {
    // 压缩
    var compressed bytes.Buffer
    writer := lzw.NewWriter(&compressed, lzw.LSB, 8)
    writer.Write(data)
    writer.Close()
    
    // 加密
    block, err := aes.NewCipher(key)
    if err != nil {
        return nil, err
    }
    
    ciphertext := make([]byte, aes.BlockSize+len(compressed.Bytes()))
    iv := ciphertext[:aes.BlockSize]
    if _, err := io.ReadFull(rand.Reader, iv); err != nil {
        return nil, err
    }
    
    stream := cipher.NewCFBEncrypter(block, iv)
    stream.XORKeyStream(ciphertext[aes.BlockSize:], compressed.Bytes())
    
    return ciphertext, nil
}

// 解密并解压
func decryptAndDecompress(ciphertext []byte, key []byte) ([]byte, error) {
    // 解密
    block, err := aes.NewCipher(key)
    if err != nil {
        return nil, err
    }
    
    if len(ciphertext) < aes.BlockSize {
        return nil, fmt.Errorf("密文太短")
    }
    
    iv := ciphertext[:aes.BlockSize]
    ciphertext = ciphertext[aes.BlockSize:]
    
    stream := cipher.NewCFBDecrypter(block, iv)
    stream.XORKeyStream(ciphertext, ciphertext)
    
    // 解压
    reader := lzw.NewReader(bytes.NewReader(ciphertext), lzw.LSB, 8)
    defer reader.Close()
    
    return io.ReadAll(reader)
}

func main() {
    data := []byte("Secret data to compress and encrypt")
    key := []byte("1234567890123456") // 16 字节 AES 密钥
    
    // 压缩并加密
    encrypted, _ := compressAndEncrypt(data, key)
    fmt.Printf("加密后大小:%d 字节\n", len(encrypted))
    
    // 解密并解压
    decrypted, _ := decryptAndDecompress(encrypted, key)
    fmt.Printf("解密后:%s\n", string(decrypted))
}

🔹 注意事项和最佳实践

1. 位序选择

  • ⚠️ 重要:压缩和解压缩必须使用相同的位序
  • ✅ GIF 图像使用 LSB
  • ✅ Unix compress 使用 MSB
  • ✅ 一般用途推荐 LSB

2. 字面量宽度

  • ✅ 通常使用 8
  • ⚠️ 不要随意更改,除非有特殊需求

3. 必须关闭写入器

  • ⚠️ 重要:忘记调用 Close() 会导致数据不完整
  • ✅ 始终使用 defer Close()

4. 错误处理

  • ✅ 检查所有错误
  • ✅ 使用 defer 确保资源释放
  • ✅ 失败时清理不完整的文件

5. 性能考虑

  • ✅ 使用缓冲 I/O 提高性能
  • ✅ LZW 适合重复数据多的场景
  • ⚠️ 压缩率不如 DEFLATE

🔥 总结

核心函数

函数说明返回值
lzw.NewWriter(w, order, litWidth)创建压缩写入器io.WriteCloser
lzw.NewReader(r, order, litWidth)创建解压缩读取器io.ReadCloser

位序类型

类型说明应用场景
LSB最低有效位优先GIF、TIFF 图像
MSB最高有效位优先Unix compress

主要特点

  • 无损压缩 👉 LZW 算法
  • 流式处理 👉 支持实时数据流
  • 位序选择 👉 LSB 或 MSB
  • 简单快速 👉 压缩解压速度快

使用场景

  • GIF 图像 👉 图像压缩(LSB)
  • TIFF 图像 👉 图像格式(LSB)
  • 简单压缩 👉 快速压缩需求
  • 学习用途 👉 理解压缩算法

与其他包配合

  • image/gif 👉 GIF 图像处理
  • bufio 👉 提高 I/O 性能
  • io 👉 Copy、ReadAll 等操作

最佳实践

  • ✅ 确保位序一致
  • ✅ 始终调用 Close()
  • ✅ 使用 defer 确保资源释放
  • ✅ 使用缓冲 I/O 提高性能
  • ✅ 完善的错误处理
  • ⚠️ 注意:压缩率不如 DEFLATE

优缺点

优点:

  • ✅ 算法简单
  • ✅ 压缩速度快
  • ✅ 内存使用低
  • ✅ 适合重复数据

缺点:

  • ❌ 压缩率较低
  • ❌ 不支持压缩级别调节
  • ❌ 应用范围有限

compress/lzw 包提供了 LZW 压缩功能,主要用于 GIF 图像处理等特定场景!