encoding/ascii85 - ASCII85 编解码
⚠️ 重要说明
Go 标准库中不包含 encoding/ascii85 包。
ASCII85 编码主要用于 PostScript 和 PDF 文件格式,Go 官方标准库并未提供此功能。如需使用 ASCII85 编码,可以考虑以下方案:
- 第三方库:使用社区实现的 ASCII85 包
- 自定义实现:根据 ASCII85 规范自行实现
- 替代方案:使用标准库中的
encoding/base64或encoding/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
编码原理
基本算法:
- 将输入数据按 4 字节分组
- 将 4 字节转换为 32 位整数
- 将 32 位整数转换为 5 个 base-85 数字
- 将每个数字映射到 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%)
与其他编码的比较
| 编码方式 | 字符集大小 | 空间效率 | 人类可读 | 主要用途 |
|---|---|---|---|---|
| ASCII85 | 85 | +25% | ✅ | PostScript、PDF |
| Base64 | 64 | +33% | ✅ | 通用(邮件、Data URI) |
| Base32 | 32 | +60% | ✅ | 文件名、口头传输 |
| Hex | 16 | +100% | ✅ | 调试、哈希显示 |
| Base85 | 85 | +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 比较
| 特性 | ASCII85 | Base64 |
|---|---|---|
| 字符集大小 | 85 | 64 |
| 空间效率 | +25% | +33% |
| 人类可读 | ✅ | ✅ |
| 标准库支持 | ❌ | ✅ |
| 应用范围 | PDF/PostScript | 通用 |
推荐方案
| 需求 | 推荐方案 |
|---|---|
| PDF 处理 | ASCII85(第三方库) |
| PostScript | ASCII85(第三方库) |
| 通用编码 | Base64(标准库) |
| URL 安全 | Base64 URL Encoding(标准库) |
| 文件名 | Base32(标准库) |
参考资料
最后更新:2026-04-03
Go 版本:Go 1.23+
重要提示:Go 标准库不包含 encoding/ascii85 包,需使用第三方实现