61、STL与序列化:JSON序列化、XML序列化、二进制序列化
序列化这个话题,说白了就是把内存里的对象“拍扁”成一串字节,方便存盘或者网络传输。反过来,从字节流恢复出对象的过程叫反序列化。
我在项目中遇到过好几次这样的场景:两个服务之间要交换数据,一个用JSON,一个用XML,还有一个老系统只认二进制。嗯,这时候你就得跟序列化打交道了。
今天咱们就聊聊,怎么用C++ STL配合一些常见手法,搞定这三种序列化格式。
为什么需要序列化?
你想想看,C++里的对象在内存里是一堆指针、虚表、成员变量。这些结构换个进程就看不懂了。序列化就是给数据一个“通用语言”。
- 跨平台通信:不同语言、不同系统之间交换数据
- 持久化存储:把程序状态存到文件里,下次启动再读回来
- 调试与日志:把复杂对象转成可读文本,方便排查问题
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"}
XML序列化
XML比JSON啰嗦,但胜在结构严谨,有Schema校验。STL里没有XML支持,不过我们可以用 std::string 和 std::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;
}
二进制序列化
二进制序列化效率最高,但可读性最差。STL的 std::ofstream 和 std::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;
}
三种序列化方式对比
| 特性 | JSON | XML | 二进制 |
|---|---|---|---|
| 可读性 | 高 | 中 | 低 |
| 性能 | 中 | 低 | 高 |
| 数据体积 | 大 | 大 | 小 |
| 跨语言 | 好 | 好 | 需约定格式 |
| STL原生支持 | 无(需第三方库) | 无(需第三方库) | 有(流式I/O) |
序列化方式选择流程图
下面这张图帮你快速决策:
STL容器序列化的通用思路
不管用哪种格式,核心思路都一样:遍历STL容器,把每个元素转换成目标格式的片段,再拼起来。
对于 std::vector、std::map、std::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]
总结
序列化是C++开发里绕不开的话题。STL虽然没直接提供JSON或XML解析器,但它的字符串流、容器、文件流为序列化打下了坚实基础。
三种方式各有适用场景:
- JSON:适合Web API、配置文件、调试输出
- XML:适合需要Schema校验、文档型数据的场景
- 二进制:适合高性能、低延迟的内部通信
嗯,今天就聊到这儿。记住一点:序列化的本质就是“把结构拍平”,理解了这一点,不管什么格式都万变不离其宗。
公众号:蓝海资料掘金营,微信deep3321