一、核心概念:什么是序列化与反序列化?
在计算机科学中,序列化(Serialization) 和 反序列化(Deserialization) 是数据持久化与传输的核心技术。
- 序列化:将内存中的数据结构(如结构体、数组等)转换为二进制字节流的过程。这个字节流可以存储到文件、数据库,或通过网络传输到其他设备。
- 反序列化:将二进制字节流还原为内存中可用数据结构的过程,是序列化的逆操作。
典型应用场景
- 数据持久化:将程序运行时的临时数据(如用户信息、配置参数)保存到文件,下次启动时恢复。
- 网络通信:不同进程/设备间通过网络传输数据时,需将数据转换为无格式的二进制流(避免文本协议的解析开销)。
- 跨平台协作:确保不同系统(如Windows/Linux)间数据格式兼容(需注意字节序问题)。
二、C 语言实现序列化与反序列化
C 语言作为面向过程的语言,没有内置的序列化库,但可以通过文件操作函数(如 fwrite
/fread
)直接操作二进制数据,实现轻量级序列化。以下是完整实现与详细解析。
1. 数据结构定义
首先定义需要序列化的目标结构体。本例以学生信息为例:
1 2 3 4 5
| typedef struct { char name[50]; // 姓名(固定长度字符串) int age; // 年龄(整型) float score; // 分数(浮点型) } Student;
|
选择固定长度的字符数组存储字符串,避免动态内存(如 char* name
)带来的序列化复杂度(需额外记录指针地址和内存长度)。
2. 序列化函数实现
序列化函数的核心是将结构体内存块整体写入二进制文件。
1 2 3 4 5 6 7 8 9 10
| void serialize(Student *student, const char *filename) { FILE *file = fopen(filename, "wb"); // "wb":二进制写模式(覆盖模式) if (file == NULL) { // 检查文件是否成功打开 perror("无法打开文件"); // 打印错误信息(如权限不足、路径错误) return; } // 写入操作:参数依次为 数据指针、单元素大小、元素数量、文件指针 fwrite(student, sizeof(Student), 1, file); fclose(file); // 关闭文件释放资源 }
|
- 关键函数:fwrite的作用是将内存中的一段连续字节写入文件。
- 第一个参数
student
:指向待写入数据的指针(结构体内存块的起始地址)。
- 第二个参数
sizeof(Student)
:单次写入的元素大小(整个结构体的字节长度)。
- 第三个参数
1
:仅写入1个这样的元素(即整个结构体)。
- 第四个参数
file
:目标文件指针。
3. 反序列化函数实现
反序列化函数从二进制文件中读取字节流,并还原为结构体实例。
1 2 3 4 5 6 7 8 9 10
| void deserialize(Student *student, const char *filename) { FILE *file = fopen(filename, "rb"); // "rb":二进制读模式 if (file == NULL) { perror("无法打开文件"); return; } // 读取操作:参数含义与 fwrite 一致 fread(student, sizeof(Student), 1, file); fclose(file); }
|
- 关键函数:
fread
与 fwrite
是一对“镜像”函数,负责从文件读取字节流到内存。
4. 主函数验证
主函数演示完整的序列化-反序列化流程:
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 26 27 28
| int main() { // 初始化原始数据 Student original_student; strncpy(original_student.name, "Alice", sizeof(original_student.name)-1); // 避免字符串溢出 original_student.name[sizeof(original_student.name)-1] = '\0'; // 手动添加终止符 original_student.age = 20; original_student.score = 95.5;
// 序列化:写入文件 const char *filename = "student_data.bin"; serialize(&original_student, filename); printf("序列化完成,数据已写入 %s ", filename); // 输出提示(无空行)
// 反序列化:读取文件 Student deserialized_student; deserialize(&deserialized_student, filename); printf("反序列化完成,读取的数据如下: "); printf("姓名: %s ", deserialized_student.name); // 输出:Alice printf("年龄: %d ", deserialized_student.age); // 输出:20 printf("分数: %.1f ", deserialized_student.score); // 输出:95.5
return 0; }
|
- 注意事项 :
- 字符串赋值使用
strncpy
而非 strcpy
,避免目标数组溢出(name
数组长度为50,需预留终止符空间)。
- 手动设置字符串终止符
\0
,确保 printf
正确输出(strncpy
不会自动添加)。
三、方案局限性与优化方向
本例的实现适用于固定内存布局的结构体,但存在以下限制:
- 动态内存不友好:若结构体包含指针(如
char* name
),fwrite
会仅保存指针地址而非实际字符串内容,反序列化后指针失效。
- 跨平台兼容性:不同系统的字节序(大端/小端)可能导致浮点数或整型数据解析错误(需手动处理字节序)。
- 可读性差:二进制文件无法直接查看内容(需借助工具如
hexdump
)。
四、完整代码
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 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 52 53 54 55
| #include <stdio.h> #include <stdlib.h> #include <string.h>
// 定义学生结构体 typedef struct { char name[50]; // 姓名(固定长度) int age; // 年龄 float score; // 分数 } Student;
// 序列化:将结构体写入二进制文件 void serialize(Student *student, const char *filename) { FILE *file = fopen(filename, "wb"); if (!file) { perror("无法打开文件"); return; } fwrite(student, sizeof(Student), 1, file); // 写入整个结构体内存块 fclose(file); }
// 反序列化:从二进制文件读取结构体 void deserialize(Student *student, const char *filename) { FILE *file = fopen(filename, "rb"); if (!file) { perror("无法打开文件"); return; } fread(student, sizeof(Student), 1, file); // 读取整个结构体内存块 fclose(file); }
int main() { // 初始化原始数据 Student original = {.name = "Alice", .age = 20, .score = 95.5}; // C99指定初始化
// 序列化 const char *filename = "student_data.bin"; serialize(&original, filename); printf("序列化完成,数据已写入 %s ", filename);
// 反序列化 Student restored; deserialize(&restored, filename); printf("反序列化结果: "); printf("姓名: %s 年龄: %d 分数: %.1f ", restored.name, restored.age, restored.score);
return 0; }
|