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
| 特性 | LZW | DEFLATE (flate) | Gzip |
|---|---|---|---|
| 算法 | LZW | DEFLATE (LZ77 + Huffman) | DEFLATE + 头部 |
| 压缩率 | 较低 | 高 | 高 |
| 压缩速度 | 快 | 中等 | 中等 |
| 解压速度 | 快 | 快 | 快 |
| 内存使用 | 低 | 中等 | 中等 |
| 主要应用 | GIF、TIFF | ZIP、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 图像处理等特定场景!