序列化和反序列化
概述
-
本文仅为本人学习过程笔记,仅用法,实际实现在于封装重载函数,尚未研究
-
编写应用程序的时候需要将程序的某些数据存储在内存中,将其写入某个文件或加ing其传输到网络中另一台计算机上实现通讯
-
过程称为序列化
-
逆过程成为反序列化
+ -
- MFC Serialization
- .Net Framework
-
原因,概述
简单来说序列化就是一种用来处理对象流的机制。所谓对象流也就是将对象的内容进行流化,流的概念这里不用多说(就是I/O)。我们可以对流化后的对象进行读写操作,也可将流化后的对象传输于网络之间(注:要想将对象传输于网络必须进行流化)! 在对对象流进行读写操作时会引发一些问题,而序列化机制正是用来解决这些问题的! ----------------------------------------------------------------------------------------- 问题的引出: 如上所述,读写对象会有什么问题呢?比如:我要将对象写入一个磁盘文件而后再将其读出来会有什么问题吗? 别急,其中一个最大的问题就是对象引用! 举个例子来说:假如我有两个类,分别是A和B,B类中含有一个指向A类对象的引用,现在我们对两个类进行实例化{ A a = new A(); B b = new B(); }。这时在内存中实际上分配 了两个空间,一个存储对象a,一个存储对象b。接下来我们想将它们写入到磁盘的一个文件中去,就在写入文件时出现了问题!因为对象b包含对对象a的引用,所以系统会自动的将a 的数据复制一份到b中,这样的话当我们从文件中恢复对象时(也就是重新加载到内存中)时,内存分配了三个空间,而对象a同时在内存中存在两份,想一想后果吧,如果我想修改对象a 的数据的话,那不是还要搜索它的每一份拷贝来达到对象数据的一致性,这不是我们所希望的! ----------------------------------------------------------------------------------------- 以下序列化机制的解决方案: 1.保存到磁盘的所有对象都获得一个序列号(1, 2, 3等等) 2.当要保存一个对象时,先检查该对象是否被保存了 3.如果以前保存过,只需写入"与已经保存的具有序列号x的对象相同"的标记,否则,保存该对象通过以上的步骤序列化机制解决了对象引用的问题!
-
- 场景
- 当你想把的内存中的对象保存到一个文件中或者数据库中时候;
- 当你想用套接字在网络上传送对象的时候;
- 当你想通过RMI传输对象的时候;
- 注意事项
- a)当一个父类实现序列化,子类自动实现序列化,不需要显式实现Serializable接口;
- b)当一个对象的实例变量引用其他对象,序列化该对象时也把引用对象进行序列化;
- c)并非所有的对象都可以序列化,原因:
1.安全方面的原因,比如一个对象拥有private,public等field,对于一个要传输的对象,比如写到文件,或者进行RMI(远程方法调用)传输等,在序列化进行传输的过程中,这个对象的private等域是不受保护的。 1. 资源分配方面的原因,比如socket,thread类,如果可以序列化 进行传输或者保存,也无法对他们进行重新的资源分配,而且,也是没有必要这样实现。
- d) 没有实现Serializable接口的父类,编写一个能够序列化的子类
1, 父类要有一个无参的constructor;
- 场景
CArchive
/*显示创建CArchive*/
CArchive archive(&theFile, CArchive::store);
-
为 CArchive 构造函数的第二个参数是指定的枚举值存档是否为存储或加载数据将使用来回文件
-
对象的 Serialize 功能通过调用 IsStoring() 功能检查此状态存档对象
- ar.isStoring()的实现,重载符号的方向与文件操作输入输出相同
-
序列化使用的时候,会自动将所有的内容序列化,包括私有变量,只要输入了一个,其他类中的所有变量都会进行序列化
- 但是具体顺序还不知道是什么
-
使用完毕之后需要关闭
/*先关闭archive,再关闭文件*/ archive.Close(); theFile.Close();
-
- 字符串存在多种存储方式
类的编写
-
头文件
#pragma once #define _AFXDLL #include <afxwin.h> #include <afx.h>
-
继承与CObject
-
声明DECLARE_SERIAL(Class) & IMPLEMENT_SERIAL(Father, CObject, 1)
-
DECLARE_SERIAL(Class)必须在序列化的类体内部
-
IMPLEMENT_SERIAL(Father, CObject, 1)必须在序列化的类体外部
class_name 类的实际名字(不用引号括起来)。 base_class_name 基类的名字(不用引号括起来)。 wSchema 一个UINT类型的版本号,将被用在存档中,使得解串行程序能够识别并处理早期版本的程序所生成的数据。它的值不能是-1。
-
两种定义方式取决于define里的写法
-
-
重写serialize()方法
virtual void Serialize(CArchive &ar); void Asd::Serialize(CArchive& ar) { CObject::Serialize(ar); // 内存为标准 // Is Storing if (ar.IsStoring()) { // Store ar << a << b << c ; } else { // Load ar >> a >> b >> c; } }
-
封装类型的序列化需要主动调用
-
序列化的时候,需要主动调用父类的序列化
-
当父类实现序列化的时候,子类如果没实现,也可以直接调用父类的序列化,但是反序列化会丢失子类的属性
- 子类序列化,父类没实现也同样,会丢失父类属性
- 没有被序列化的数据,在反序列化的时候都会丢失
工程样例
#pragma once
/**
* 定义数据对象
*/
#define _AFXDLL //MFC程序的宏定义
#include <afxwin.h> //MFC程序头文件
#include <afx.h> //MFC程序头文件
class Person : public CObject
{
DECLARE_SERIAL(Person) //第一个宏的位置,参数为当前类名
public:
Person();
~Person();
public:
//重写了Serialize成员函数,实现对象序列化读写操作
virtual void Serialize(CArchive& ar);
//重写操作符友元函数
//friend CArchive& AFXAPI operator<<(CArchive& ar, Person& src);
//friend CArchive& AFXAPI operator>>(CArchive& ar, Person& src);
//定义对象数据
public:
long m_id;
CString m_name;
int m_age;
};
#include "People.h"
//关键宏定义:第二个宏的位置,
//第一个参数为当前类名,
//第二个参数为父类类名,
//第三个参数为该类的特定整型标识,该标识将用来解序(重新实例化),最小为0
IMPLEMENT_SERIAL(Person, CObject, 1)
Person::Person() :CObject()
{
m_id = 0;
m_age = 0;
}
Person::~Person()
{
}
void Person::Serialize(CArchive& ar)
{
//序列化读写基类对象数据
CObject::Serialize(ar);
//序列化读写当前对象数据
if (ar.IsStoring())//判断读写操作,写操作,将数据写入到文件,加载为ar.IsLoading()
{
ar << m_id << m_name << m_age; //功能类似cout、cin的IO数据流操作
}
else //读操作,将文件读入到内存
{
ar >> m_id >> m_name >> m_age;
}
}
CArchive& AFXAPI operator>>(CArchive& ar, Person& src)
{
return ar >> src.m_id >> src.m_name >> src.m_age;
}
CArchive& AFXAPI operator<<(CArchive& ar, Person& src)
{
return ar << src.m_id << src.m_name << src.m_age;
}
ar << a;
ar << a << b << c;
ar << a << b << c << e;
ar << a << e << b << c;
ar >> a;
ar >> a >> b >> c;
ar >> a >> b >> c >> e;
ar >> a >> e >> b >> c;
// TSTMFC序列化读写.cpp : 此文件包含 "main" 函数。程序执行将在此处开始并结束。
#include <iostream>
#include "People.h"
using namespace std;
//#define WRITE //读写控制宏 读:READ 写:WRITE
#define READ
//测试入口函数
int main()
{
#ifdef WRITE
cout << "执行写文件操作\n" << endl;
CFile file(_T("../person.txt"), CFile::modeCreate | CFile::modeWrite); //定义一个文件流对象
CArchive ar(&file, CArchive::store); //定义一个序列化对象和文件流对象绑定并指定归档方式为储存,加载的方式为CArchive::load
Person person;
person.m_id = 12345;
person.m_name = "zhangsan";
person.m_age = 12;
person.Serialize(ar);
ar.Close();
file.Close();
#endif
#ifdef READ
cout << "执行读文件操作\n" << endl;
CFile file(_T("../person.txt"), CFile::modeRead); //定义一个文件流对象
CArchive ar(&file, CArchive::load); //定义一个序列化对象和文件流对象绑定并指定归档方式为储存,加载的方式为CArchive::load
Person person;
person.Serialize(ar);
cout << person.m_id << "|" << person.m_name << "|" << person.m_age << endl;
ar.Close();
file.Close();
cin.get();
#endif // DEBUG