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

encoding/ascii85 - ASCII85 编解码

⚠️ 重要说明

Go 标准库中不包含 encoding/ascii85

ASCII85 编码主要用于 PostScript 和 PDF 文件格式,Go 官方标准库并未提供此功能。如需使用 ASCII85 编码,可以考虑以下方案:

  1. 第三方库:使用社区实现的 ASCII85 包
  2. 自定义实现:根据 ASCII85 规范自行实现
  3. 替代方案:使用标准库中的 encoding/base64encoding/base85

本文档将介绍 ASCII85 编码的原理、使用方法,以及 Go 语言中的实现方案。


ASCII85 编码概述

什么是 ASCII85

ASCII85(也称为 Base85)是一种基于 85 个可打印 ASCII 字符的二进制到文本的编码方式。

特点

  • 📦 高效编码:使用 5 个 ASCII 字符表示 4 个字节(效率约 125%)
  • 📄 PostScript/PDF 标准:Adobe PostScript 和 PDF 文件格式使用
  • 🔤 85 个字符:使用 ASCII 33-117(! 到 u)
  • 比 Base64 紧凑:节省约 20% 的空间

字符集

!"#$%&'()*+,-./0123456789:;<=>?@ABCDEFGHIJKLMNOPQRSTUVWXYZ[\]^_`abcdefghijklmnopqrstuv

编码原理

基本算法

  1. 将输入数据按 4 字节分组
  2. 将 4 字节转换为 32 位整数
  3. 将 32 位整数转换为 5 个 base-85 数字
  4. 将每个数字映射到 ASCII85 字符集

编码效率

4 字节二进制数据 = 32 位
5 个 ASCII85 字符 = 5 × log2(85) ≈ 32.04 位
空间效率:5/4 = 1.25(增加 25%)

对比 Base64:
4 字节二进制数据 = 32 位
4 个 Base64 字符 = 4 × 6 = 24 位(需要填充)
空间效率:4/3 ≈ 1.33(增加 33%)

与其他编码的比较

编码方式字符集大小空间效率人类可读主要用途
ASCII8585+25%PostScript、PDF
Base6464+33%通用(邮件、Data URI)
Base3232+60%文件名、口头传输
Hex16+100%调试、哈希显示
Base8585+25%Z85、Ascii85 变体

Go 中的 ASCII85 实现

方案 1:使用第三方库

推荐的第三方库

1. github.com/yourbasic/ascii85

package main

import (
    "github.com/yourbasic/ascii85"
    "fmt"
)

func main() {
    data := []byte("Hello, World!")
    
    // 编码
    encoded := make([]byte, ascii85.EncodedLen(len(data)))
    n := ascii85.Encode(encoded, data)
    fmt.Printf("编码:%s\n", string(encoded[:n]))
    
    // 解码
    decoded := make([]byte, ascii85.DecodedLen(len(encoded)))
    n, err := ascii85.Decode(decoded, encoded[:n])
    if err != nil {
        fmt.Printf("解码失败:%v\n", err)
        return
    }
    fmt.Printf("解码:%s\n", string(decoded[:n]))
}

2. github.com/panjf2000/ascii85

package main

import (
    "github.com/panjf2000/ascii85"
    "fmt"
)

func main() {
    data := []byte("Hello, World!")
    
    // 编码
    encoded := ascii85.EncodeToString(data)
    fmt.Printf("编码:%s\n", encoded)
    
    // 解码
    decoded, err := ascii85.DecodeString(encoded)
    if err != nil {
        fmt.Printf("解码失败:%v\n", err)
        return
    }
    fmt.Printf("解码:%s\n", string(decoded))
}

方案 2:自定义实现

以下是一个简单的 ASCII85 编解码器实现:

package ascii85

import (
    "errors"
)

// ASCII85 字符集
const alphabet = "!\"#$%&'()*+,-./0123456789:;<=>?@ABCDEFGHIJKLMNOPQRSTUVWXYZ[\\]^_`abcdefghijklmnopqrstuv"

// 编码长度计算
func EncodedLen(n int) int {
    return (n + 3) / 4 * 5
}

// 解码长度计算
func DecodedLen(n int) int {
    return n / 5 * 4
}

// Encode 编码
func Encode(dst, src []byte) int {
    if len(src) == 0 {
        return 0
    }
    
    i := 0
    for len(src) >= 4 {
        // 读取 4 字节
        v := uint32(src[0])<<24 | uint32(src[1])<<16 | uint32(src[2])<<8 | uint32(src[3])
        
        // 特殊情况:全零
        if v == 0 {
            dst[i] = 'z'
            i++
            src = src[4:]
            continue
        }
        
        // 转换为 5 个 base-85 数字
        for j := 4; j >= 0; j-- {
            dst[i+j] = alphabet[v%85]
            v /= 85
        }
        i += 5
        src = src[4:]
    }
    
    // 处理剩余字节
    if len(src) > 0 {
        v := uint32(0)
        for j := 0; j < len(src); j++ {
            v |= uint32(src[j]) << uint(24-j*8)
        }
        
        // 编码
        for j := 4; j >= 0; j-- {
            if j >= len(src)+1 {
                break
            }
            dst[i+j] = alphabet[v%85]
            v /= 85
        }
        i += len(src) + 1
    }
    
    return i
}

// Decode 解码
func Decode(dst, src []byte) (int, error) {
    if len(src) == 0 {
        return 0, nil
    }
    
    i := 0
    for len(src) >= 5 {
        // 特殊情况:'z'
        if src[0] == 'z' {
            if len(dst) < i+4 {
                return 0, errors.New("缓冲区太小")
            }
            dst[i] = 0
            dst[i+1] = 0
            dst[i+2] = 0
            dst[i+3] = 0
            i += 4
            src = src[5:]
            continue
        }
        
        // 读取 5 个字符
        v := uint32(0)
        for j := 0; j < 5; j++ {
            c := src[j]
            if c < '!' || c > 'u' {
                return 0, errors.New("无效的 ASCII85 字符")
            }
            v = v*85 + uint32(c-'!')
        }
        
        // 写入 4 字节
        if len(dst) < i+4 {
            return 0, errors.New("缓冲区太小")
        }
        dst[i] = byte(v >> 24)
        dst[i+1] = byte(v >> 16)
        dst[i+2] = byte(v >> 8)
        dst[i+3] = byte(v)
        i += 4
        src = src[5:]
    }
    
    return i, nil
}

// EncodeToString 编码为字符串
func EncodeToString(src []byte) string {
    dst := make([]byte, EncodedLen(len(src)))
    n := Encode(dst, src)
    return string(dst[:n])
}

// DecodeString 从字符串解码
func DecodeString(s string) ([]byte, error) {
    dst := make([]byte, DecodedLen(len(s)))
    n, err := Decode(dst, []byte(s))
    if err != nil {
        return nil, err
    }
    return dst[:n], nil
}

完整示例

示例 1:基本编解码

package main

import (
    "fmt"
    "github.com/panjf2000/ascii85"
)

func main() {
    // 原始数据
    data := []byte("Hello, ASCII85!")
    fmt.Printf("原始数据:%s\n", string(data))
    fmt.Printf("原始长度:%d 字节\n\n", len(data))
    
    // 编码
    encoded := ascii85.EncodeToString(data)
    fmt.Printf("ASCII85 编码:%s\n", encoded)
    fmt.Printf("编码长度:%d 字符\n\n", len(encoded))
    
    // 解码
    decoded, err := ascii85.DecodeString(encoded)
    if err != nil {
        fmt.Printf("解码失败:%v\n", err)
        return
    }
    fmt.Printf("ASCII85 解码:%s\n", string(decoded))
    fmt.Printf("解码长度:%d 字节\n", len(decoded))
    
    // 验证
    if string(decoded) == string(data) {
        fmt.Println("\n✓ 编解码成功!")
    } else {
        fmt.Println("\n✗ 编解码失败!")
    }
}

示例 2:文件编解码

package main

import (
    "fmt"
    "io/ioutil"
    "github.com/panjf2000/ascii85"
)

// 编码文件
func EncodeFile(inputPath, outputPath string) error {
    // 读取文件
    data, err := ioutil.ReadFile(inputPath)
    if err != nil {
        return fmt.Errorf("读取文件失败:%v", err)
    }
    
    // 编码
    encoded := ascii85.EncodeToString(data)
    
    // 写入编码后的文件
    err = ioutil.WriteFile(outputPath, []byte(encoded), 0644)
    if err != nil {
        return fmt.Errorf("写入文件失败:%v", err)
    }
    
    fmt.Printf("文件已编码:%s -> %s\n", inputPath, outputPath)
    fmt.Printf("原始大小:%d 字节\n", len(data))
    fmt.Printf("编码大小:%d 字节\n", len(encoded))
    
    return nil
}

// 解码文件
func DecodeFile(inputPath, outputPath string) error {
    // 读取编码文件
    encoded, err := ioutil.ReadFile(inputPath)
    if err != nil {
        return fmt.Errorf("读取文件失败:%v", err)
    }
    
    // 解码
    decoded, err := ascii85.DecodeString(string(encoded))
    if err != nil {
        return fmt.Errorf("解码失败:%v", err)
    }
    
    // 写入解码后的文件
    err = ioutil.WriteFile(outputPath, decoded, 0644)
    if err != nil {
        return fmt.Errorf("写入文件失败:%v", err)
    }
    
    fmt.Printf("文件已解码:%s -> %s\n", inputPath, outputPath)
    fmt.Printf("编码大小:%d 字节\n", len(encoded))
    fmt.Printf("解码大小:%d 字节\n", len(decoded))
    
    return nil
}

func main() {
    // 编码示例
    err := EncodeFile("input.bin", "output.asc")
    if err != nil {
        fmt.Printf("编码失败:%v\n", err)
    }
    
    // 解码示例
    err = DecodeFile("output.asc", "restored.bin")
    if err != nil {
        fmt.Printf("解码失败:%v\n", err)
    }
}

示例 3:流式编解码

package main

import (
    "bytes"
    "fmt"
    "io"
    "github.com/panjf2000/ascii85"
)

// Ascii85Encoder ASCII85 编码器
type Ascii85Encoder struct {
    w   io.Writer
    buf []byte
}

// NewEncoder 创建编码器
func NewEncoder(w io.Writer) *Ascii85Encoder {
    return &Ascii85Encoder{w: w}
}

// Write 写入数据
func (e *Ascii85Encoder) Write(p []byte) (int, error) {
    e.buf = append(e.buf, p...)
    
    // 处理完整的 4 字节块
    for len(e.buf) >= 4 {
        encoded := ascii85.EncodeToString(e.buf[:4])
        _, err := e.w.Write([]byte(encoded))
        if err != nil {
            return 0, err
        }
        e.buf = e.buf[4:]
    }
    
    return len(p), nil
}

// Close 关闭编码器
func (e *Ascii85Encoder) Close() error {
    // 处理剩余数据
    if len(e.buf) > 0 {
        encoded := ascii85.EncodeToString(e.buf)
        _, err := e.w.Write([]byte(encoded))
        return err
    }
    return nil
}

// Ascii85Decoder ASCII85 解码器
type Ascii85Decoder struct {
    r   io.Reader
    buf []byte
}

// NewDecoder 创建解码器
func NewDecoder(r io.Reader) *Ascii85Decoder {
    return &Ascii85Decoder{r: r}
}

// Read 读取数据
func (d *Ascii85Decoder) Read(p []byte) (int, error) {
    // 实现略复杂,需要根据实际情况处理
    // 这里仅作为示例
    return 0, io.EOF
}

func main() {
    // 编码示例
    var encoded bytes.Buffer
    encoder := NewEncoder(&encoded)
    
    data := []byte("Hello, Stream!")
    encoder.Write(data)
    encoder.Close()
    
    fmt.Printf("编码结果:%s\n", encoded.String())
    
    // 解码示例(需要根据实际情况实现)
    // decoder := NewDecoder(&encoded)
    // decoded := make([]byte, len(data))
    // decoder.Read(decoded)
}

示例 4:PDF 文件处理

package main

import (
    "bytes"
    "fmt"
    "io/ioutil"
    "regexp"
    "github.com/panjf2000/ascii85"
)

// 提取 PDF 中的 ASCII85 数据
func ExtractASCII85FromPDF(pdfData []byte) ([][]byte, error) {
    // PDF 中 ASCII85 数据的格式:<~ ... ~>
    re := regexp.MustCompile(`<~([0-9a-zA-Z]+)~>`)
    matches := re.FindAllSubmatch(pdfData, -1)
    
    var results [][]byte
    for _, match := range matches {
        if len(match) > 1 {
            decoded, err := ascii85.DecodeString(string(match[1]))
            if err != nil {
                return nil, err
            }
            results = append(results, decoded)
        }
    }
    
    return results, nil
}

// 创建包含 ASCII85 数据的 PDF 流
func CreateASCII85Stream(data []byte) []byte {
    encoded := ascii85.EncodeToString(data)
    
    var buf bytes.Buffer
    buf.WriteString("<~")
    buf.WriteString(encoded)
    buf.WriteString("~>")
    
    return buf.Bytes()
}

func main() {
    // 示例:编码数据
    originalData := []byte("This is binary data for PDF embedding.")
    pdfStream := CreateASCII85Stream(originalData)
    
    fmt.Printf("原始数据:%s\n", string(originalData))
    fmt.Printf("PDF 流:%s\n", string(pdfStream))
    
    // 示例:从 PDF 提取数据
    pdfContent := []byte(`
        /Length 50
        stream
        <~9jqo^BlbD-BleB1DJ+*+F(f,q~>
        endstream
    `)
    
    extracted, err := ExtractASCII85FromPDF(pdfContent)
    if err != nil {
        fmt.Printf("提取失败:%v\n", err)
        return
    }
    
    fmt.Printf("\n提取的数据块数量:%d\n", len(extracted))
    for i, data := range extracted {
        fmt.Printf("数据块 %d: %x\n", i, data)
    }
}

示例 5:性能对比

package main

import (
    "encoding/base64"
    "fmt"
    "testing"
    "github.com/panjf2000/ascii85"
)

func BenchmarkEncoding(b *testing.B) {
    data := []byte("Hello, World! This is a test of ASCII85 encoding performance.")
    
    b.Run("ASCII85", func(b *testing.B) {
        for i := 0; i < b.N; i++ {
            ascii85.EncodeToString(data)
        }
    })
    
    b.Run("Base64", func(b *testing.B) {
        for i := 0; i < b.N; i++ {
            base64.StdEncoding.EncodeToString(data)
        }
    })
}

func BenchmarkDecoding(b *testing.B) {
    data := []byte("Hello, World! This is a test of ASCII85 encoding performance.")
    ascii85Encoded := ascii85.EncodeToString(data)
    base64Encoded := base64.StdEncoding.EncodeToString(data)
    
    b.Run("ASCII85", func(b *testing.B) {
        for i := 0; i < b.N; i++ {
            ascii85.DecodeString(ascii85Encoded)
        }
    })
    
    b.Run("Base64", func(b *testing.B) {
        for i := 0; i < b.N; i++ {
            base64.StdEncoding.DecodeString(base64Encoded)
        }
    })
}

func CompareEfficiency() {
    data := []byte("Hello, World! This is a comparison of encoding efficiency.")
    
    ascii85Encoded := ascii85.EncodeToString(data)
    base64Encoded := base64.StdEncoding.EncodeToString(data)
    
    fmt.Printf("原始数据:%d 字节\n", len(data))
    fmt.Printf("ASCII85 编码:%d 字符 (效率:%.2f%%)\n", 
        len(ascii85Encoded), float64(len(ascii85Encoded))/float64(len(data))*100)
    fmt.Printf("Base64 编码:%d 字符 (效率:%.2f%%)\n", 
        len(base64Encoded), float64(len(base64Encoded))/float64(len(data))*100)
    fmt.Printf("ASCII85 比 Base64 节省:%.2f%%\n", 
        float64(len(base64Encoded)-len(ascii85Encoded))/float64(len(base64Encoded))*100)
}

func main() {
    CompareEfficiency()
}

注意事项

⚠️ 标准库限制

// ❌ 错误:Go 标准库中没有 encoding/ascii85
import "encoding/ascii85"  // 编译错误

// ✅ 正确:使用第三方库
import "github.com/panjf2000/ascii85"

⚠️ 字符集兼容性

ASCII85 有多个变体,字符集可能不同:

// Adobe ASCII85(PostScript/PDF)
// 字符集:! 到 u

// Z85(ZeroMQ)
// 字符集:略有不同,更适合 C 语言字符串

// 确保使用正确的变体

⚠️ 特殊字符处理

// 'z' 字符表示 4 个零字节
// 这是一个特殊情况,可以节省空间

// 编码全零数据
zeros := []byte{0, 0, 0, 0}
encoded := ascii85.EncodeToString(zeros)
fmt.Printf("编码:%s\n", encoded)  // 输出:z

// 解码 'z'
decoded, _ := ascii85.DecodeString("z")
fmt.Printf("解码:%x\n", decoded)  // 输出:00000000

总结

ASCII85 特点

特性说明
字符集85 个可打印 ASCII 字符(! 到 u)
空间效率5 个字符表示 4 个字节(+25%)
主要用途PostScript、PDF 文件格式
特殊情况‘z’ 表示 4 个零字节
Go 支持需要第三方库

与 Base64 比较

特性ASCII85Base64
字符集大小8564
空间效率+25%+33%
人类可读
标准库支持
应用范围PDF/PostScript通用

推荐方案

需求推荐方案
PDF 处理ASCII85(第三方库)
PostScriptASCII85(第三方库)
通用编码Base64(标准库)
URL 安全Base64 URL Encoding(标准库)
文件名Base32(标准库)

参考资料


最后更新:2026-04-03
Go 版本:Go 1.23+
重要提示:Go 标准库不包含 encoding/ascii85 包,需使用第三方实现