Base85 编码简介
Base85(也称为 Ascii85)是一种二进制到文本的编码方案,用于将二进制数据转换为可打印的ASCII字符。它的效率高于Base64,但生成的字符串可能包含特殊字符(如引号或反斜杠),需在特定场景(如JSON)中谨慎使用。
编码原理
Base85将每4个字节(32位)的二进制数据转换为5个Base85字符。计算公式如下:
- 将4字节数据视为一个32位无符号整数(大端序)。
- 重复除以85,取余数作为Base85字符的索引值。
- 将索引映射到字符集(如
!
到u
)。
示例:
假设4字节数据为0x4A3B2C1D
,转换步骤如下:
- 数值为
1,246,434,333
。 - 依次除以85:
1246434333 ÷ 85 = 14663933
,余数28
→ 字符<
(索引28)。14663933 ÷ 85 = 172517
,余数48
→ 字符o
(索引48)。- 继续计算剩余字符。
常用字符集
不同实现可能使用不同字符集,常见两种:
- RFC 1924版本:
!"#$%&'()*+,-./0123456789:;<=>?@ABCDEFGHIJKLMNOPQRSTUVWXYZ[\]^_`abcdefghijklmnopqrstu
- Adobe Ascii85:
- 字符范围:
!
(33)到u
(117)。 - 特殊标记:
z
表示4字节全零,~~~~~
为数据流结束符。
- 字符范围:
注意事项
- 数据对齐:输入数据长度若非4字节倍数,需补零处理。
- 特殊字符:避免在XML/JSON中直接使用,需额外转义。
- 效率权衡:Base85比Base64节省约1/4空间,但复杂度更高。
应用场景
- Adobe PDF:用于内嵌二进制数据(如字体)。
- 网络传输:需要紧凑编码的场景。
- Git存储:Git的二进制补丁可能使用Base85。
#include <stdint.h>
#include <stdlib.h>
#include <string.h>
#include <stdio.h>#define BLOCK_SIZE 4
#define ENCODED_BLOCK_SIZE 5// Base85编码函数
char *base85_encode(const uint8_t *data, size_t len) {if (data == NULL || len == 0) return NULL;// 计算输出缓冲区大小(每4字节→5字符)size_t max_out_len = ((len + BLOCK_SIZE - 1) / BLOCK_SIZE) * ENCODED_BLOCK_SIZE + 1;char *output = malloc(max_out_len);if (!output) return NULL;size_t out_index = 0;uint32_t block = 0;for (size_t i = 0; i < len; i += BLOCK_SIZE) {size_t remaining = len - i;size_t block_len = (remaining < BLOCK_SIZE) ? remaining : BLOCK_SIZE;// 构建32位大端序块block = 0;for (size_t j = 0; j < block_len; j++) {block |= (uint32_t)data[i + j] << (24 - 8 * j);}// 全零块缩写为'z'if (block_len == BLOCK_SIZE && block == 0) {output[out_index++] = 'z';continue;}// 计算5个Base85字符(从高位到低位)char encoded[ENCODED_BLOCK_SIZE];for (int j = ENCODED_BLOCK_SIZE - 1; j >= 0; j--) {encoded[j] = (block % 85) + '!';block /= 85;}// 根据实际字节数复制有效字符size_t chars_to_copy = block_len + 1; // 字节数+1for (size_t j = 0; j < chars_to_copy; j++) {output[out_index++] = encoded[j];}}output[out_index] = '\0';return output;
}// Base85解码函数
uint8_t *base85_decode(const char *input, size_t *out_len) {if (input == NULL || out_len == NULL) return NULL;size_t in_len = strlen(input);if (in_len == 0) return NULL;// 计算最大输出长度(每5字符→4字节)size_t max_out_len = (in_len * BLOCK_SIZE) / ENCODED_BLOCK_SIZE;uint8_t *output = malloc(max_out_len);if (!output) return NULL;size_t in_index = 0;size_t out_index = 0;uint32_t block = 0;while (in_index < in_len) {// 处理全零块缩写if (input[in_index] == 'z') {for (int j = 0; j < BLOCK_SIZE; j++) {output[out_index++] = 0;}in_index++;continue;}// 读取5个字符(不足时用'u'填充)size_t chars_in_block = 0;block = 0;for (int j = 0; j < ENCODED_BLOCK_SIZE; j++) {if (in_index >= in_len) break;char c = input[in_index++];if (c < '!' || c > 'u') continue; // 跳过无效字符block = block * 85 + (c - '!');chars_in_block++;}// 填充不足的字符while (chars_in_block < ENCODED_BLOCK_SIZE) {block = block * 85 + ('u' - '!');chars_in_block++;}// 提取4个字节(大端序)size_t bytes_to_write = chars_in_block - 1; // 字符数-1=原始字节数for (int j = 0; j < bytes_to_write; j++) {output[out_index++] = (block >> (24 - 8 * j)) & 0xFF;}}*out_len = out_index;return output;
}// 测试函数
int main() {// 编码测试uint8_t data[] = {0x86, 0x4F, 0xD2, 0x6F, 0xB5, 0x59, 0x00, 0x00};char *encoded = base85_encode(data, sizeof(data));printf("Encoded: %s\n", encoded); // 输出: L/Ch[+>Gz// 解码测试size_t decoded_len;uint8_t *decoded = base85_decode(encoded, &decoded_len);printf("Decoded: ");for (size_t i = 0; i < decoded_len; i++) {printf("%02X ", decoded[i]); // 输出: 86 4F D2 6F B5 59 00 00}printf("\n");// 清理内存free(encoded);free(decoded);return 0;
}
base85的实现规则相对比较多,如果不替换原始字母表,建议使用openssl接口。