Base 64 编码
图片,邮件附件
将任何二进制的文本编译成纯 ASCII 的字符串(7 bit 表示键盘上直接能打印出来的字符)
base 64 在 ASCII 中挑选了 64 个「可显示字符」的子集
encode
因为是 64,所以是 6 bit,将原本的二进制数据按照 6 bit 一组查表得到对应 ASCII,通过 ASCII 文本传输,会带来大约 1/3 的额外体积开销。
padding:如果 input 的二进制个数不能被 6 整除,补零之后,再加一个 = 作为 padding(填充编码)
decode
拿到 ASCII 之后,每 6 位 bit 解析,再每 8 位转为 char 即可。
优化
很多优化手段,
- 64 个字符转为 bit
- 固定长度分配内存
详见 chrome 浏览器 atob 的源码 (opens in a new tab)实现。。魔法
// 摘录自源码 原地将 4 byte 转为 3 byte
// 4-byte to 3-byte conversion
out_length -= (out_length + 3) / 4;
if (!out_length)
return false;
unsigned sidx = 0;
unsigned didx = 0;
if (out_length > 1) {
while (didx < out_length - 2) {
out[didx] = (((out[sidx] << 2) & 255) | ((out[sidx + 1] >> 4) & 003));
out[didx + 1] =
(((out[sidx + 1] << 4) & 255) | ((out[sidx + 2] >> 2) & 017));
out[didx + 2] = (((out[sidx + 2] << 6) & 255) | (out[sidx + 3] & 077));
sidx += 4;
didx += 3;
}
}
浏览器上的 base64 转换函数
字符串 to base64:btoa('coyote')
=> Y295b3Rl
反之:atob('Y295b3Rl')
=> coyote
代码
#include <iostream>
#include <string>
static constexpr const char base64Chars[] =
"ABCDEFGHIJKLMNOPQRSTUVWXYZabcdefghigklmnopqrstuvwxyz0123456789+/";
// 7 bit ascii 映射到 base64Chars 的位置
static const unsigned char decMap[128] = {
64, 64, 64, 64, 64, 64, 64, 64, 64, 64, 64, 64, 64, 64,
64, 64, 64, 64, 64, 64, 64, 64, 64, 64, 64, 64, 64, 64,
64, 64, 64, 64, 64, 64, 64, 64, 64, 64, 64, 64, 64, 64,
64, 62, 64, 64, 64, 63, 52, 53, 54, 55, 56, 57, 58, 59,
60, 61, 64, 64, 64, 64, 64, 64, 64, 0, 1, 2, 3, 4,
5, 6, 7, 8, 9, 10, 11, 12, 13, 14, 15, 16, 17, 18,
19, 20, 21, 22, 23, 24, 25, 64, 64, 64, 64, 64, 64, 26,
27, 28, 29, 30, 31, 32, 33, 34, 64, 36, 37, 38, 39, 40,
41, 42, 43, 44, 45, 46, 47, 48, 49, 50, 51, 64, 64, 64,
64, 64};
// calculate those from js code
// const res = Array.from({ length: 128 }, (_, i) => {
// const base64Chars = // 这个可以单独写在外面 为了写在一个函数里面..
// 'ABCDEFGHIJKLMNOPQRSTUVWXYZabcdefghigklmnopqrstuvwxyz0123456789+/';
// const char = String.fromCharCode(i);
// const indexOfBC = base64Chars.indexOf(char);
// return indexOfBC === -1 ? 64 : indexOfBC;
// });
uint8_t decode(const uint8_t c)
{
for (const char &bc : base64Chars)
{
if (c == bc)
{
return &bc - base64Chars;
}
}
throw std::invalid_argument("invalid base64 input");
}
// 学一个新的 std string_view c++17 https://www.learncpp.com/cpp-tutorial/an-introduction-to-stdstring_view/
std::string base64Decode(const std::string_view input)
{
std::string output;
unsigned int buf = 0;
unsigned int bufSize = 0;
// unsigned char
for (uint8_t c : input)
{
if (c > 127)
{
break;
}
if (c == '=') // 默认 padding 在末尾 直接结束
{
break;
}
// uint8_t sextet = decode(c); // 00XX XXXX 虽然是 8 bit 但是只有低 6 位有内容
uint8_t sextet = decMap[c];
if (sextet >= 64)
{
throw std::invalid_argument("invalid base64 input");
}
// populate output 只要记录 6 位 让 buffer 每次塞进去 6 位
buf = (buf << 6) | sextet; // + sextet also ok
bufSize += 6;
if (bufSize >= 8) // 满足一个字节 就撤出 8 位
{
// 取出前 8 位 char 只能截断低 8 位? sure...
output.push_back((char)(buf >> (bufSize - 8)));
bufSize -= 8;
}
// 111100110000111111
}
return output;
}
int main()
{
std::string out = base64Decode("Y295b3Rl"); // coyote
std::cout << out << std::endl;
// std::cout << (char)(0b00111101) << std::endl; // =
// std::cout << (char)(0b1100111101) << std::endl; // =
return 0;
}