Base64 编码 & URI/URL 详解
一、Base64 是什么
1.1 一句话定义
Base64 是一种把二进制数据转换成纯文本的编码方式。用 64 个可打印字符(字母、数字、+、/)来表示任意字节数据。
1.2 为什么需要它
计算机里很多数据是二进制的——图片、音频、加密密钥、Protobuf 消息等。但很多传输协议只支持文本:
| 协议/场景 | 限制 |
|---|---|
| JSON | 只能传 UTF-8 文本,不能传原始字节 |
| HTTP Header | 纯 ASCII,二进制值会出错 |
| SMTP(邮件) | 最早只支持 7-bit ASCII |
HTML<img> | src必须是 URL 或 Data URI |
Base64 就是翻译器:把"机器读的字节"翻成"协议能传送的字符串"。
1.3 在我的项目里干什么用
本地 test.png → [二进制 PNG 数据] → Base64 编码 → "iVBORw0KGgo..." ↓ 塞进 JSON body 发给火山引擎 API ↓ 服务器反向解码还原图片没有 Base64,你就得先把图片上传到 OSS,拿到 URL,再把 URL 传给 API——多一步。Base64 让你直接把本地文件"嵌入"请求里。
二、Base64 是怎么编出来的
2.1 核心思想:3 字节 → 4 字符
原始: 3 个字节 = 24 个比特 Base64: 24 个比特 ÷ 6 = 4 个字符每个 Base64 字符代表 6 个比特(0-63),对应字符集里的一个位置。
2.2 字符集(64 个字符)
索引 字符 索引 字符 索引 字符 索引 字符 0 A 16 Q 32 g 48 w 1 B 17 R 33 h 49 x 2 C 18 S 34 i 50 y 3 D 19 T 35 j 51 z 4 E 20 U 36 k 52 0 5 F 21 V 37 l 53 1 6 G 22 W 38 m 54 2 7 H 23 X 39 n 55 3 8 I 24 Y 40 o 56 4 9 J 25 Z 41 p 57 5 10 K 26 a 42 q 58 6 11 L 27 b 43 r 59 7 12 M 28 c 44 s 60 8 13 N 29 d 45 t 61 9 14 O 30 e 46 u 62 + 15 P 31 f 47 v 63 /2.3 手动编码示例
把"Man"三个字符编成 Base64:
步骤 1: 取每个字符的 ASCII 码 M → 77 (0x4D) → 01001101 a → 97 (0x61) → 01100001 n → 110 (0x6E) → 01101110 步骤 2: 拼成 24 位二进制流 01001101 01100001 01101110 步骤 3: 切成 4 个 6 位 010011 010110 000101 101110 ↓ ↓ ↓ ↓ 步骤 4: 每 6 位转成十进制 (0-63) 010011 → 19 010110 → 22 000101 → 5 101110 → 46 步骤 5: 查字符集 19 → T 22 → W 5 → F 46 → u 结果: "TWFu"2.4 填充规则(=号)
当原始字节数不是 3 的倍数时:
| 剩余字节数 | 填充 |
|---|---|
| 1 字节(8 位) | 取 6 位 + 填 2 个零 → 2 个 Base64 字符 +== |
| 2 字节(16 位) | 取 12 位 → 3 个 Base64 字符 += |
| 3 字节(24 位) | 刚好 4 个字符,不填 |
示例:只编码一个字节M(77) → 01001101
010011 01.... ← 只有 8 位,后 4 位不存在 010011 010000 ← 后面填 0000 凑够 12 位 ↓ ↓ 19 16 T Q 结果: "TQ==" (两个 `=` 表示"原始数据只有 1 字节,解码时忽略填充位")三、URI 和 URL 的关系
3.1 一句话
URL 是 URI 的一种。URI 是"标识某个资源",URL 在 URI 的基础上多了"怎么获取它"。
3.2 层级关系
URI (Uniform Resource Identifier - 统一资源标识符) ├── URL (Uniform Resource Locator - 统一资源定位符) │ "这个资源在哪里、怎么拿到" │ 例: https://example.com/photo.png │ 文件::///home/user/photo.png │ ftp://server/file.zip │ └── URN (Uniform Resource Name - 统一资源名称) "这个资源叫什么名字(但不告诉你它在哪)" 例: urn:isbn:0-486-27557-4 (一本书的 ISBN) urn:uuid:6e69285a-1461-459e... (一个 UUID)3.3 关键区别
| URI | URL | URN | |
|---|---|---|---|
| 概念 | “资源叫这个” | “去这里拿” | “它就叫这个名字” |
| 是否包含位置 | 不一定 | 必须 | 不包含 |
| 能直接访问吗 | 不一定 | 能 | 不能 |
| 例子 | https://a.com/x.png | ✓ | ✗ |
data:image/png;base64,... | ✗ | ✗ | |
urn:isbn:0451450523 | ✗ | ✓ | |
mailto:hi@example.com | ✗ | ✗ |
所有 URL 都是 URI,但不是所有 URI 都是 URL。
3.4 Data URI 的特殊位置
Data URI 是最特殊的一类 URI:
data:image/png;base64,iVBORw0...- 它是 URI:唯一标识了一个资源(这张图片)
- 它不是 URL:没有告诉你去哪里下载,资源就在字符串本身里
- 它不是 URN:没有给资源命名,而是直接附带了完整数据
可以理解为自包含的 URI——资源即是标识符,标识符即是资源。
3.5 你项目里两个 URI 的对比
// 请求中:告诉 API "参考图长这样""data:image/png;base64,iVBORw0KGgo..."// Data URI (非 URL)// ↑ 图片数据直接嵌在字符串里// 响应中:API 返回 "生成结果在这里""https://ark-volces.com/tmp/abc123.png"// URL (也是 URI)// ↑ 指向远程服务器上的一个文件四、C++ 中实现 Base64 编码
4.1 完整实现(零依赖,~15 行)
conststd::string BASE64_CHARS="ABCDEFGHIJKLMNOPQRSTUVWXYZabcdefghijklmnopqrstuvwxyz0123456789+/";std::stringbase64_encode(constunsignedchar*data,size_t len){std::string result;result.reserve(4*((len+2)/3));// 预分配,避免反复扩容for(size_t i=0;i<len;i+=3){// 把最多 3 个字节拼成 24 位整数unsignedintn=(unsignedint)data[i]<<16;if(i+1<len)n|=(unsignedint)data[i+1]<<8;if(i+2<len)n|=(unsignedint)data[i+2];// 24 位切成 4 个 6 位,查表得到 4 个字符result.push_back(BASE64_CHARS[(n>>18)&0x3F]);// 位[23:18]result.push_back(BASE64_CHARS[(n>>12)&0x3F]);// 位[17:12]result.push_back((i+1<len)?BASE64_CHARS[(n>>6)&0x3F]// 位[11:6]:'=');// 填充result.push_back((i+2<len)?BASE64_CHARS[n&0x3F]// 位[5:0]:'=');// 填充}returnresult;}4.2 逐行解释
预分配:
result.reserve(4*((len+2)/3));| len | (len+2)/3 | × 4 | 含义 |
|---|---|---|---|
| 1 | 1 | 4 | 1 字节 → 4 字符(含==) |
| 2 | 1 | 4 | 2 字节 → 4 字符(含=) |
| 3 | 1 | 4 | 3 字节 → 4 字符 |
| 4 | 2 | 8 | 4 字节 → 8 字符 |
| 100 | 34 | 136 | 100 字节 → 136 字符 |
(len+2)/3是ceil(len/3)的整除技巧。
24 位拼接:
unsignedintn=(unsignedint)data[i]<<16;if(i+1<len)n|=(unsignedint)data[i+1]<<8;if(i+2<len)n|=(unsignedint)data[i+2];位[23:16] ← data[i] 位[15:8] ← data[i+1] (如果存在) 位[7:0] ← data[i+2] (如果存在)6 位切片 + 查表:
24 位: [23:18] [17:12] [11:6] [5:0] ↓ ↓ ↓ ↓ 操作: >>18 >>12 >>6 >>0 掩码: &0x3F &0x3F &0x3F &0x3F>>18 & 0x3F意思是"向右移 18 位,只保留最低 6 位"。
0x3F=00111111(二进制)= 63(十进制),刚好取低 6 位。
条件填充:
(i+1<len)?BASE64_CHARS[...]:'='- 原始第 2 个字节不存在 → 输出
= - 原始第 3 个字节不存在 → 输出
= - 解码时看到
=就知道:这轮只有 1 或 2 个真实字节
4.3 解码实现(对称反向)
std::vector<unsignedchar>base64_decode(conststd::string&encoded){// 构建反向查找表: 字符 → 6 位值intreverse[256]={};for(inti=0;i<64;i++){reverse[(unsignedchar)BASE64_CHARS[i]]=i;}std::vector<unsignedchar>result;result.reserve(encoded.size()*3/4);intval=0,bits=-8;for(unsignedcharc:encoded){if(c=='=')break;// 遇到 = 停止val=(val<<6)|reverse[c];// 每 6 位拼进 24 位缓冲区bits+=6;if(bits>=0){result.push_back((val>>bits)&0xFF);// 取出一个完整字节bits-=8;}}returnresult;}五、Base64 的主要用途
5.1 用途总览
| 场景 | 举例 |
|---|---|
| API 传图片/文件 | 图生图 API 的image字段(就是你的项目) |
| Data URI 内嵌 | HTML<img src="data:image/png;base64,..."> |
| 邮件附件 | MIMEContent-Transfer-Encoding: base64 |
| JWT Token | eyJhbGci...的 header/payload 部分 |
| 密钥存储 | SSH 公钥-----BEGIN PUBLIC KEY----- |
| URL 安全传输 | 二进制 ID 转成 URL-safe 字符串(+/→-_) |
| 配置文件 | k8s Secret 里的证书、docker registry 认证 |
5.2 为什么不用 Base16 (hex)
| Base16 | Base64 | |
|---|---|---|
| 开销 | 膨胀 100%(1 字节 → 2 字符) | 膨胀 33%(3 字节 → 4 字符) |
| 示例 | FF00AB12... | /wCrEg... |
| 优点 | 人眼直接可读 | 更紧凑 |
Base64 是"可读性"和"体积"之间的最优折中。
5.3 常见变体
| 变体 | 区别 | 用途 |
|---|---|---|
| 标准 Base64 | 用+/,=填充 | JSON、XML、一般场景 |
| URL-Safe Base64 | +/→-_,去掉= | URL 参数、文件名、JWT |
| Base64 without padding | 省略尾部= | 节省几个字符 |
六、一张图总结
┌─────────────────────────────────────────────────────────┐ │ URI (标识资源) │ │ ┌──────────────────────┐ ┌──────────────┐ │ │ │ URL (定位资源) │ │ URN (命名资源)│ │ │ │ │ │ │ │ │ │ https://x.com/a.png │ │ urn:isbn:123 │ │ │ │ file:///home/a.png │ │ │ │ │ └──────────────────────┘ └──────────────┘ │ │ │ │ ┌──────────────────────────┐ │ │ │ Data URI (自包含资源) │ 不属于 URL 也不属于 URN │ │ │ │ │ │ │ data:image/png;base64, │ │ │ │ iVBORw0KGgoAAAA... │ │ │ │ ─────────── ────────── │ │ │ │ 告诉服务器: 实际图片 │ │ │ │ "这是PNG" 数据编码 │ │ │ └──────────────────────────┘ │ └─────────────────────────────────────────────────────────┘ Base64 编码过程: 原始图片 (二进制) → [3字节一组] → [4字符] → JSON 文本 ↓ ↓ ↓ ↓ 无法直接塞 JSON 切成 24 位 查表映射 拼进 body "iVBORw0..."