43、C++项目实战:Base64编解码工具

Base64 编码,说白了就是一种把二进制数据转成文本的“翻译规则”。

我最早接触它,是在做网络协议解析的时候。那时候要传一张图片,结果发现 HTTP 协议里有些字符会被截断或误解。嗯,Base64 就是来解决这个问题的——它只用了 64 个“安全字符”,就能表示任意二进制数据。

Base64 的原理,一句话讲清楚

Base64 的核心思想很简单:把 3 个字节(24 位)拆成 4 组,每组 6 位,然后映射到一张字符表上

你想想看,6 位二进制能表示 0~63,正好对应 64 个字符。这就是“Base64”名字的由来。

核心公式:

3 字节 → 4 个 Base64 字符

如果字节数不是 3 的倍数,末尾补 0,并用 '=' 填充。

字符表长这样:

索引字符索引字符索引字符索引字符
0A16Q32g48w
1B17R33h49x
........................
62+63/填充=

我个人习惯把这张表记成:A-Z(0-25)、a-z(26-51)、0-9(52-61)、+(62)、/(63)。

编码流程,我画了张图

下面这张 SVG 图,把编码过程拆成了 4 步。你看完就明白了。

Base64 编码流程 原始二进制数据 3 字节(24 位) 按 6 位分组 4 组 6 位二进制 每组范围 0~63 查表映射 Base64 字符表 A-Z a-z 0-9 + / 输出 4 个 ASCII 字符 末尾不足补 '=' 特殊情况:字节数不是 3 的倍数 补 0 字节,输出对应数量的 '=' 示例:输入 "Man"(3 字节)→ 编码为 "TWFu"(4 字符) 输入 "M"(1 字节)→ 编码为 "TQ=="(补 2 个 '=')

代码实现,我踩过的坑

直接上代码。我写了一个完整的 Base64 编解码类,里面加了不少注释。

#include <iostream>
#include <string>
#include <vector>
#include <cstdint>

class Base64 {
private:
    // 编码表
    static const char kEncodeTable[65];
    // 解码表,-1 表示非法字符
    static const int kDecodeTable[256];

public:
    // 编码:输入字节数组,输出 Base64 字符串
    static std::string Encode(const std::vector<uint8_t>& data) {
        std::string result;
        size_t i = 0;
        size_t len = data.size();

        while (i + 3 <= len) {
            // 取 3 个字节,拼成 24 位整数
            uint32_t val = (data[i] << 16) | (data[i+1] << 8) | data[i+2];
            // 拆成 4 个 6 位索引
            result += kEncodeTable[(val >> 18) & 0x3F];
            result += kEncodeTable[(val >> 12) & 0x3F];
            result += kEncodeTable[(val >> 6) & 0x3F];
            result += kEncodeTable[val & 0x3F];
            i += 3;
        }

        // 处理剩余字节
        size_t remain = len - i;
        if (remain == 1) {
            uint32_t val = data[i] << 16;
            result += kEncodeTable[(val >> 18) & 0x3F];
            result += kEncodeTable[(val >> 12) & 0x3F];
            result += "==";
        } else if (remain == 2) {
            uint32_t val = (data[i] << 16) | (data[i+1] << 8);
            result += kEncodeTable[(val >> 18) & 0x3F];
            result += kEncodeTable[(val >> 12) & 0x3F];
            result += kEncodeTable[(val >> 6) & 0x3F];
            result += "=";
        }

        return result;
    }

    // 解码:输入 Base64 字符串,输出原始字节数组
    static std::vector<uint8_t> Decode(const std::string& input) {
        std::vector<uint8_t> result;
        size_t len = input.size();

        // 去掉末尾的 '='
        size_t padding = 0;
        while (len > 0 && input[len-1] == '=') {
            padding++;
            len--;
        }

        for (size_t i = 0; i + 4 <= len; i += 4) {
            int a = kDecodeTable[(uint8_t)input[i]];
            int b = kDecodeTable[(uint8_t)input[i+1]];
            int c = kDecodeTable[(uint8_t)input[i+2]];
            int d = kDecodeTable[(uint8_t)input[i+3]];

            // 检查非法字符
            if (a == -1 || b == -1 || c == -1 || d == -1) {
                // 我曾经在这里吃过亏,输入里混了换行符
                // 建议先做预处理,去掉空白字符
                return {};
            }

            uint32_t val = (a << 18) | (b << 12) | (c << 6) | d;
            result.push_back((val >> 16) & 0xFF);
            result.push_back((val >> 8) & 0xFF);
            result.push_back(val & 0xFF);
        }

        // 处理末尾的填充
        if (padding > 0) {
            size_t last_chunk = len - 4;
            int a = kDecodeTable[(uint8_t)input[last_chunk]];
            int b = kDecodeTable[(uint8_t)input[last_chunk+1]];
            int c = (padding >= 2) ? 0 : kDecodeTable[(uint8_t)input[last_chunk+2]];
            int d = (padding >= 1) ? 0 : kDecodeTable[(uint8_t)input[last_chunk+3]];

            uint32_t val = (a << 18) | (b << 12) | (c << 6) | d;
            result.push_back((val >> 16) & 0xFF);
            if (padding < 2) result.push_back((val >> 8) & 0xFF);
            if (padding < 1) result.push_back(val & 0xFF);
        }

        return result;
    }
};

// 静态成员定义
const char Base64::kEncodeTable[65] =
    "ABCDEFGHIJKLMNOPQRSTUVWXYZabcdefghijklmnopqrstuvwxyz0123456789+/";

const int Base64::kDecodeTable[256] = {
    -1,-1,-1,-1,-1,-1,-1,-1,-1,-1,-1,-1,-1,-1,-1,-1,
    -1,-1,-1,-1,-1,-1,-1,-1,-1,-1,-1,-1,-1,-1,-1,-1,
    -1,-1,-1,-1,-1,-1,-1,-1,-1,-1,-1,62,-1,-1,-1,63,
    52,53,54,55,56,57,58,59,60,61,-1,-1,-1, 0,-1,-1,
    -1, 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,-1,-1,-1,-1,-1,
    -1,26,27,28,29,30,31,32,33,34,35,36,37,38,39,40,
    41,42,43,44,45,46,47,48,49,50,51,-1,-1,-1,-1,-1,
    // 其余 128 个位置全部填 -1(省略)
};

小技巧:

解码时,我建议先对输入做一次“清洗”——去掉换行符、空格等空白字符。因为很多网络协议会在 Base64 字符串里插入换行,你不处理就会解码失败。

测试用例,跑一遍就放心

写完了代码,总得验证一下。我一般会写几个测试用例:

int main() {
    // 测试 1:标准 3 字节
    std::vector<uint8_t> data1 = {'M', 'a', 'n'};
    std::string enc1 = Base64::Encode(data1);
    std::cout << "Encode 'Man' = " << enc1 << std::endl;  // 输出 TWFu

    // 测试 2:1 字节,带填充
    std::vector<uint8_t> data2 = {'M'};
    std::string enc2 = Base64::Encode(data2);
    std::cout << "Encode 'M' = " << enc2 << std::endl;   // 输出 TQ==

    // 测试 3:解码验证
    auto dec1 = Base64::Decode("TWFu");
    std::string str1(dec1.begin(), dec1.end());
    std::cout << "Decode 'TWFu' = " << str1 << std::endl; // 输出 Man

    // 测试 4:空数据
    std::vector<uint8_t> empty;
    std::string enc_empty = Base64::Encode(empty);
    std::cout << "Encode empty = '" << enc_empty << "'" << std::endl;

    return 0;
}

注意:

我曾经在解码时忘记处理 '=' 填充,结果最后几个字节全乱了。调试了半天才发现是填充逻辑写错了。所以,填充处理一定要仔细,尤其是末尾字节数不足的情况。

性能优化,能快一点是一点

如果你要处理大文件(比如几百 MB 的图片),逐字节编解码可能有点慢。我分享几个优化思路:

  • 批量处理:一次读 3KB 数据,编解码完再写回去,减少函数调用开销。
  • 查表法加速:解码时用 256 大小的数组直接查,比 switch-case 快 3~5 倍。
  • SIMD 指令:如果平台支持,可以用 SSE/AVX 一次处理 16 字节,吞吐量能翻好几倍。
  • 内存复用:避免频繁分配 std::vector,用预分配缓冲区。

说白了,Base64 本身不复杂,但细节决定成败。尤其是填充和非法字符处理,稍不留神就踩坑。

嗯,这个工具写好了,以后处理邮件附件、图片传输、甚至 JWT Token 解析,都能直接拿来用。

公众号:蓝海资料掘金营,微信deep3321