61、STL与序列化:JSON序列化、XML序列化、二进制序列化

序列化这个话题,说白了就是把内存里的对象“拍扁”成一串字节,方便存盘或者网络传输。反过来,从字节流恢复出对象的过程叫反序列化。

我在项目中遇到过好几次这样的场景:两个服务之间要交换数据,一个用JSON,一个用XML,还有一个老系统只认二进制。嗯,这时候你就得跟序列化打交道了。

今天咱们就聊聊,怎么用C++ STL配合一些常见手法,搞定这三种序列化格式。

为什么需要序列化?

你想想看,C++里的对象在内存里是一堆指针、虚表、成员变量。这些结构换个进程就看不懂了。序列化就是给数据一个“通用语言”。

  • 跨平台通信:不同语言、不同系统之间交换数据
  • 持久化存储:把程序状态存到文件里,下次启动再读回来
  • 调试与日志:把复杂对象转成可读文本,方便排查问题
我的习惯:开发阶段先用JSON,方便调试;上线前如果对性能有要求,再换成二进制序列化。

JSON序列化

JSON是现在最流行的文本序列化格式。C++标准库没有直接提供JSON解析器,但我们可以用STL的字符串流和容器来模拟一个轻量级的方案。

我个人习惯用 std::ostringstream 来拼接JSON字符串。看个例子:

#include <iostream>
#include <sstream>
#include <map>
#include <vector>
#include <string>

std::string toJson(const std::map<std::string, std::string>& data) {
    std::ostringstream oss;
    oss << "{";
    bool first = true;
    for (const auto& [key, value] : data) {
        if (!first) oss << ", ";
        oss << "\"" << key << "\": \"" << value << "\"";
        first = false;
    }
    oss << "}";
    return oss.str();
}

int main() {
    std::map<std::string, std::string> user = {
        {"name", "Alice"},
        {"role", "engineer"},
        {"city", "Beijing"}
    };
    std::cout << toJson(user) << std::endl;
    return 0;
}

输出:

{"name": "Alice", "role": "engineer", "city": "Beijing"}
注意:手写JSON解析器容易踩坑,比如转义字符、Unicode编码。生产环境建议用成熟的库,比如 nlohmann/json 或 RapidJSON。

XML序列化

XML比JSON啰嗦,但胜在结构严谨,有Schema校验。STL里没有XML支持,不过我们可以用 std::stringstd::ostringstream 来生成简单的XML。

我曾经在一个遗留系统里见过用纯字符串拼接XML的做法,虽然不优雅,但够用:

#include <iostream>
#include <sstream>
#include <vector>

struct Book {
    std::string title;
    std::string author;
    int year;
};

std::string toXml(const std::vector<Book>& books) {
    std::ostringstream oss;
    oss << "<?xml version=\"1.0\" encoding=\"UTF-8\"?>\n";
    oss << "<library>\n";
    for (const auto& book : books) {
        oss << "  <book>\n";
        oss << "    <title>" << book.title << "</title>\n";
        oss << "    <author>" << book.author << "</author>\n";
        oss << "    <year>" << book.year << "</year>\n";
        oss << "  </book>\n";
    }
    oss << "</library>\n";
    return oss.str();
}

int main() {
    std::vector<Book> books = {
        {"C++ Primer", "Lippman", 2012},
        {"Effective Modern C++", "Meyers", 2014}
    };
    std::cout << toXml(books) << std::endl;
    return 0;
}
避坑指南:XML里如果内容包含 < > & 等特殊字符,必须转义。我曾经因为没转义 & 符号,导致下游解析器报错,排查了半天。

二进制序列化

二进制序列化效率最高,但可读性最差。STL的 std::ofstreamstd::ifstream 配合 write/read 方法,可以直接操作内存字节。

来看一个用 std::vector<char> 做二进制缓冲的例子:

#include <iostream>
#include <fstream>
#include <vector>
#include <cstring>

struct Player {
    char name[32];
    int level;
    float hp;
};

// 序列化到字节流
std::vector<char> serialize(const Player& p) {
    std::vector<char> buffer(sizeof(Player));
    std::memcpy(buffer.data(), &p, sizeof(Player));
    return buffer;
}

// 反序列化
Player deserialize(const std::vector<char>& buffer) {
    Player p;
    std::memcpy(&p, buffer.data(), sizeof(Player));
    return p;
}

int main() {
    Player alice = {"Alice", 42, 100.0f};
    auto bytes = serialize(alice);

    // 写入文件
    std::ofstream file("player.bin", std::ios::binary);
    file.write(bytes.data(), bytes.size());
    file.close();

    // 从文件读取
    std::ifstream infile("player.bin", std::ios::binary);
    std::vector<char> read_bytes(sizeof(Player));
    infile.read(read_bytes.data(), read_bytes.size());
    Player restored = deserialize(read_bytes);

    std::cout << "Name: " << restored.name << ", Level: " 
              << restored.level << ", HP: " << restored.hp << std::endl;
    return 0;
}
警告:二进制序列化有陷阱。比如结构体对齐、字节序(大端/小端)、指针不能直接序列化。我踩过最深的坑是跨平台传输时,不同机器的int长度不一样。

三种序列化方式对比

特性 JSON XML 二进制
可读性
性能
数据体积
跨语言 需约定格式
STL原生支持 无(需第三方库) 无(需第三方库) 有(流式I/O)

序列化方式选择流程图

下面这张图帮你快速决策:

需要序列化? 需要跨语言/跨平台? 是 → 文本格式 否 → 二进制格式 需要Schema校验? XML JSON 性能优先? Protobuf 手写二进制

STL容器序列化的通用思路

不管用哪种格式,核心思路都一样:遍历STL容器,把每个元素转换成目标格式的片段,再拼起来。

对于 std::vectorstd::mapstd::string 这些常见容器,可以写模板函数来统一处理:

template<typename T>
std::string serializeContainer(const T& container) {
    std::ostringstream oss;
    oss << "[";
    bool first = true;
    for (const auto& item : container) {
        if (!first) oss << ", ";
        oss << item;
        first = false;
    }
    oss << "]";
    return oss.str();
}

// 用法
std::vector<int> vec = {1, 2, 3, 4};
std::cout << serializeContainer(vec) << std::endl;  // 输出 [1, 2, 3, 4]
我的建议:如果项目里序列化场景很多,别自己造轮子。Google的Protobuf、Apache的Thrift、或者Cap'n Proto都是久经考验的方案。它们底层都用了STL的字符串和容器,但帮你处理了边界情况。

总结

序列化是C++开发里绕不开的话题。STL虽然没直接提供JSON或XML解析器,但它的字符串流、容器、文件流为序列化打下了坚实基础。

三种方式各有适用场景:

  • JSON:适合Web API、配置文件、调试输出
  • XML:适合需要Schema校验、文档型数据的场景
  • 二进制:适合高性能、低延迟的内部通信

嗯,今天就聊到这儿。记住一点:序列化的本质就是“把结构拍平”,理解了这一点,不管什么格式都万变不离其宗。


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