分享
 
 
 

实例解析C++/CLI的串行化

王朝c/c++·作者佚名  2008-06-01
窄屏简体版  字體: |||超大  

串行化可使对象被转换为某种外部的形式,比如以文件存储的形式供程序使用,或通过程序间的通讯发送到另一个处理过程。转换为外部形式的过程称为"串行化",而逆过程称为"反串行化"。

简介

请看例1中的示例,其将多个对象类型的值写入到一个新的磁盘文件中,关闭文件,接着再把这些值重新读取到内存中。

例1:

using namespace System;

using namespace System::IO;

using namespace System::Runtime::Serialization::Formatters::Binary;

int main()

{

array<int>^ intArray = {10, 20, 30};

array<float,2>^ floatArray = {

{1.2F, 2.4F},

{3.5F, 6.8F},

{8.4F, 9.7F}

};

DateTime dt = DateTime::Now;

Console::WriteLine("dt >{0}<", dt);

/*1*/ BinaryFormatter^ formatter = gcnew BinaryFormatter;

//将数据串行化到一个文件

/*2*/ Stream^ file = File::Open("Sr01.ser", FileMode::Create);

/*3a*/ formatter->Serialize(file, "Hello");

/*3b*/ formatter->Serialize(file, intArray);

/*3c*/ formatter->Serialize(file, floatArray);

/*3d*/ formatter->Serialize(file, true);

/*3e*/ formatter->Serialize(file, dt);

/*3f*/ formatter->Serialize(file, 1000);

/*3g*/ formatter->Serialize(file, L'X');

/*3h*/ formatter->Serialize(file, 1.23456F);

/*4*/ file->Close();

//从文件中反串行化数据--即读取数据

/*5*/ file = File::Open("Sr01.ser", FileMode::Open);

/*6a*/ String^ s = static_cast<String^>(formatter->Deserialize(file));

Console::WriteLine("String >{0}<", s);

/*6b*/ array<int>^ newIntArray =

static_cast<array<int>^>(formatter->Deserialize(file));

Console::WriteLine("newIntArray:");

for (int i = 0; i < newIntArray->Length; ++i)

{

Console::Write(" {0}", newIntArray[i]);

}

Console::WriteLine();

/*6c*/ array<float,2>^ newFloatArray =

static_cast<array<float,2>^>(formatter->Deserialize(file));

Console::WriteLine("newFloatArray:");

for (int i = 0; i < 3; ++i)

{

for (int j = 0; j < 2; ++j)

{

Console::Write(" {0}", newFloatArray[i,j]);

}

Console::WriteLine();

}

/*6d*/ bool b = static_cast<bool>(formatter->Deserialize(file));

Console::WriteLine("bool >{0}<", b);

/*6e*/ DateTime newDT = static_cast<DateTime>(formatter->Deserialize(file));

Console::WriteLine("newDT >{0}<", newDT);

/*6f*/ int v = static_cast<int>(formatter->Deserialize(file));

Console::WriteLine("int >{0}<", v);

/*6g*/ wchar_t c = static_cast<wchar_t>(formatter->Deserialize(file));

Console::WriteLine("wchar_t >{0}<", c);

/*6h*/ float f = static_cast<float>(formatter->Deserialize(file));

Console::WriteLine("float >{0}<", f);

/*7*/ file->Close();

}

在标记1中,我们定义了一个BinaryFormatter类型的变量,此种类型的任意对象都可以二进制的形式进行串行与反串行化。

在标记2中,用指定的名称创建了一个新的文件,后缀 .ser没有非凡的意思,这是约定俗成的表示这是一个串行化数据文件。从标记3a至3h,表示一个对象被串行化至文件中。在字符串的情况下,每个字符都被写入;在数组的情况下,所有元素都被写入;在日期时间的情况下,类型中包含的所有数据及有关依靠项都被写入;在为原始类型值的情况下,它们先被装箱,然后对应的对象被写入。上述动作中,串行化只需要接收一个Object^类型参数的对象即可。

通过调用Deserialize函数,可取回串行化后的数据,如标记6a中所示;因为此函数返回一个Object^类型的值,所以需要把它转换为相应的值。程序的输出如插1所示:

插1:例1中串行化、反串行化的输出

String >Hello<

newIntArray

10 20 30

newFloatArray:

1.2 2.4

3.5 6.8

8.4 9.7

bool >True<

newDT >9/29/2005 3:25:44 PM<

int >1000<

wchar_t >X<

float >1.23456<

串行化包含引用的对象

在前一个例子中,我们对相关类型进行了简单的读写。那么,假如一个对象中包含了其他对象的句柄呢?试想有一个超过两万字的字典,存储在一个能通过键值索引的集合中,而在标准模板库中,就提供了一个这样的集合--哈希表(Hashtable),如例2中所示:

例2:

using namespace System;

using namespace System::IO;

using namespace System::Collections;

using namespace System::Runtime::Serialization::Formatters::Binary;

int main()

{

/*1*/ Hashtable^ dictionary = gcnew Hashtable(21000);

StreamReader^ inStream = File::OpenText("dictionary.txt"); //打开字典文件

String^ str;

while ((str = inStream->ReadLine()) != nullptr)

{

/*2*/ dictionary->Add(str, nullptr);

}

inStream->Close();

/*3*/ Console::WriteLine("Dictionary contains {0} entries", dictionary->Count);

BinaryFormatter^ formatter = gcnew BinaryFormatter();

Stream^ file = File::Open("dictionary.ser", FileMode::Create);

/*4*/ formatter->Serialize(file, dictionary);

file->Close();

}

在标记1中,我们先分配了一个初始化为21000个条目的哈希表(这样做只是为了加快处理速度,在条目相加时不需要重新进行分配),接着从一个文本文件中,一次一行地读入字,并将其添加到标记2的哈希表中。请注重,在定义中,哈希表的每个条目都由(键/值)对组成。但在我们的程序中,键也是值,所以在第二个参数中使用了nullprt。

哈希表中的键值必须是唯一的,而添加进来的任何类型的对象都必须重载System::对象名 GetHashCode函数--字符串也一样。

一旦文件中所有的字被读取并添加到哈希表中,就可通过一个简单的Serialize调用,把哈希表写到磁盘上,如标记4所示。在例3中,我们读入这个字典,并在其中查找用户提供的字,插2是对应的输出。

例3:

using namespace System;

using namespace System::IO;

using namespace System::Collections;

using namespace System::Runtime::Serialization::Formatters::Binary;

int main()

{

BinaryFormatter^ formatter = gcnew BinaryFormatter;

Stream^ file = File::Open("dictionary.ser", FileMode::Open);

/*1*/ Hashtable^ dictionary = static_cast<Hashtable^>(formatter->Deserialize(file));

file->Close();

/*2*/ Console::WriteLine("Dictionary contains {0} entries", dictionary->Count);

String^ Word;

while (true)

{

Console::Write("Enter a word: ");

word = Console::ReadLine();

if (word == nullptr)

{

break;

}

/*3*/ Console::WriteLine("{0}{1} found", word, (dictionary->Contains(word) ? "" : " not"));

}

}

插2:使用反串行化进行字典查找

Dictionary contains 20159 entries

Enter a word: house

house found

Enter a word: houses

houses not found

Enter a word: brick

brick found

Enter a word: manly

manly not found

此处最重要的是,我们能在单个函数调用中,串行、反串行化任意大小、任意复杂性的对象。

处理多个句柄

当我们传递一个对象的句柄给Serialize时,似乎会在底层对对象进行一个复制,那么,实际情况真的是这样吗?假设我们把包含有多个句柄的一个对象写入到其他对象中,或者我们调用Serialize两次,每次都给它同一个对象的句柄呢?我们真的想得到同一对象的多个副本吗?在例4中演示了这个过程:

例4:

using namespace System;

using namespace System::IO;

using namespace System::Runtime::Serialization::Formatters::Binary;

/*1*/ [Serializable]

ref class Employee { /* ... */};

int main()

{

Employee^ emp1 = gcnew Employee();

Employee^ emp2 = gcnew Employee();

Employee^ emp3 = emp2;

/*2a*/ Console::WriteLine("emp1 == emp2 is {0}", (emp1 == emp2));

/*2b*/ Console::WriteLine("emp2 == emp3 is {0}", (emp2 == emp3));

/*2c*/ Console::WriteLine("emp1 == emp3 is {0}", (emp1 == emp3));

array<Object^>^ list = gcnew array<Object^>(2);

list[0] = emp1;

list[1] = list[0];

/*2d*/ Console::WriteLine("list[0] == list[1] is {0}", (list[0] == list[1]));

/*2e*/ Console::WriteLine("list[0] == emp1 is {0}", (list[0] == emp1));

/*2f*/ Console::WriteLine("list[1] == emp1 is {0}", (list[1] == emp1));

//将数据串行化到文件

BinaryFormatter^ formatter = gcnew BinaryFormatter;

Stream^ file = File::Open("Sr03.ser", FileMode::Create);

/*3a*/ formatter->Serialize(file, emp1);

/*3b*/ formatter->Serialize(file, emp2);

/*3c*/ formatter->Serialize(file, emp3);

/*3d*/ formatter->Serialize(file, list);

file->Close();

//从文件中反串行化数据--即读取数据

file = File::Open("Sr03.ser", FileMode::Open);

/*4a*/ emp1 = static_cast<Employee^>(formatter->Deserialize(file));

/*4b*/ emp2 = static_cast<Employee^>(formatter->Deserialize(file));

/*4c*/ emp3 = static_cast<Employee^>(formatter->Deserialize(file));

/*4d*/ list = static_cast<array<Object^>^>(formatter->Deserialize(file));

file->Close();

/*5a*/ Console::WriteLine("emp1 == emp2 is {0}", (emp1 == emp2));

/*5b*/ Console::WriteLine("emp2 == emp3 is {0}", (emp2 == emp3));

/*5c*/ Console::WriteLine("emp1 == emp3 is {0}", (emp1 == emp3));

/*5d*/ Console::WriteLine("list[0] == list[1] is {0}", (list[0] == list[1]));

/*5e*/ Console::WriteLine("list[0] == emp1 is {0}", (list[0] == emp1));

/*5f*/ Console::WriteLine("list[1] == emp1 is {0}", (list[1] == emp1));

}

在本例中,我们想对Employee类型(在标记1中的用户自定义类型)的对象进行串行化,必须把Serializable属性附加到这个类型上。假如我们试图串行化一个没有标明此属性的类对象,将会抛出一个System::Runtime::Serialization::SerializationException类型的异常。串行化之前的程序输出如插3所示:

插3:串行化之前例4的输出

emp1 == emp2 is False

emp2 == emp3 is True

emp1 == emp3 is False

list[0] == list[1] is True

list[0] == emp1 is True

list[1] == emp1 is True

我们对四个目标进行了串行化,前两个代表了不同的Employee对象,而第三个是对第二个的引用,第四个为包含两个元素的数组,这两个元素均引用第一个Employee对象。程序的输出表明了它们之间的这些关系,反串行化之后的输出见插4:

插4:反串行化之后例4的输出

emp1 == emp2 is False

emp2 == emp3 is False

emp1 == emp3 is False

list[0] == list[1] is True

list[0] == emp1 is False

list[1] == emp1 is False

注重,现在第三个Employee句柄已不再是一个指向第二个Employee对象的句柄了,类似地,尽管list[0]与list[1]都引用同一个Empolyee对象,但对象已不是我们取回的第一个对象了。

在此应看到,当多个对象逐个串行化之后,它们是相关联的,而当反串行化之后,它们的关系并没有因此而恢复,但是,对象内部的关系仍然被维持。

自定义的串行化

默认情况下,当一个对象被串行化时,所有的非静态实例字段都会被写入,并在反串行化期间顺序读回;然而,对包含静态字段的类,这可能会导致一个问题。

在例5中使用了Point类,其不但包含了用于追踪每个Point x与y坐标的实例变量,而且还会跟踪在程序执行期间创建的Point数目。例如,在例5中,通过显示构造函数调用,创建了4个Point,并将它们串行化到磁盘;当它们被反串行化时,又创建了4个新的Point,因此Point总数现在为8,插5中是程序的输出:

例5:

using namespace System;

using namespace System::IO;

using namespace System::Runtime::Serialization::Formatters::Binary;

int main()

{

Console::WriteLine("PointCount: {0}", Point::PointCount);

Point^ p1 = gcnew Point(15, 10);

Point^ p2 = gcnew Point(-2, 12);

array<Point^>^ p3 = {gcnew Point(18, -5), gcnew Point(25, 19)};

Console::WriteLine("PointCount: {0}", Point::PointCount);

BinaryFormatter^ formatter = gcnew BinaryFormatter;

Stream^ file = File::Open("Point.ser", FileMode::Create);

formatter->Serialize(file, p1);

formatter->Serialize(file, p2);

formatter->Serialize(file, p3);

file->Close();

file = File::Open("Point.ser", FileMode::Open);

Point^ p4 = static_cast<Point^>(formatter->Deserialize(file));

Console::WriteLine("PointCount: {0}", Point::PointCount);

Point^ p5 = static_cast<Point^>(formatter->Deserialize(file));

Console::WriteLine("PointCount: {0}", Point::PointCount);

array<Point^>^ p6 = static_cast<array<Point^>^>(formatter->Deserialize(file));

Console::WriteLine("PointCount: {0}", Point::PointCount);

file->Close();

Console::WriteLine("p1: {0}, p4: {1}", p1, p4);

Console::WriteLine("p2: {0}, p5: {1}", p2, p5);

Console::WriteLine("p3[0]: {0}, p6[0]: {1}", p3[0], p6[0]);

Console::WriteLine("p3[1]: {0}, p6[1]: {1}", p3[1], p6[1]);

}

插5:反串行化创建了4个新的Point

PointCount: 0

PointCount: 4

PointCount: 5

PointCount: 6

PointCount: 8

p1: (15,10), p4: (15,10)

p2: (-2,12), p5: (-2,12)

p3[0]: (18,-5), p6[0]: (18,-5)

p3[1]: (25,19), p6[1]: (25,19)

当调用Point类的公有构造函数来构造一个新对象时,Point计数字段也会相应增长;而当我们反串行化一个或多个Point时,问题发生了,对Point的Deserialize调用实际上创建了一个新的Point对象,但它并没有为这些对象调用任何的构造函数啊。另外要明确一点,即使新Point数被增量1 ,PointCount也不会自动增长。我们可重载由接口ISerializable(从System::Runtime::Serialization)实现的默认的串行与反串行动作;这个接口需要定义一个调用GetObjectData的函数,这个函数就可以答应我们重载串行化过程。

GetObjectData函数的目的是,以串行化一个父类对象所需的数据,增加一个SerializationInfo对象,在此,名称、值、类型信息都被提供给AddValue函数,并由对象作为第二个参数。名称字符串可为任意,只要它在这种类型的串行化中唯一就行了。(假如使用了两个相同的名称,会抛出SerializationException异常。)

假如要重载反串行化过程,必须定义另一个构造函数,注重这个构造为私有类型,因为它只会被反串行化机制所调用,没有从外部访问的必要。

串行化的格式

以上所有串行化的例子当中,我们使用了BinaryFormatter类型,其以某种能被高效地处理的压缩格式来存储数据;然而,其他格式也能做到这点,例如,可使用一个SOAP,SOAP(Simple Object Access Protocol--简单对象访问协议)是一种用于在Web上交换结构化及类型信息的简单的、基于XML的协议,该协议未包含任何应用程序或传输语义,所以它具有高度模块化及扩展性的特点。当然,大家也能创建其他的自定义格式。

 
 
 
免责声明:本文为网络用户发布,其观点仅代表作者个人观点,与本站无关,本站仅提供信息存储服务。文中陈述内容未经本站证实,其真实性、完整性、及时性本站不作任何保证或承诺,请读者仅作参考,并请自行核实相关内容。
2023年上半年GDP全球前十五强
 百态   2023-10-24
美众议院议长启动对拜登的弹劾调查
 百态   2023-09-13
上海、济南、武汉等多地出现不明坠落物
 探索   2023-09-06
印度或要将国名改为“巴拉特”
 百态   2023-09-06
男子为女友送行,买票不登机被捕
 百态   2023-08-20
手机地震预警功能怎么开?
 干货   2023-08-06
女子4年卖2套房花700多万做美容:不但没变美脸,面部还出现变形
 百态   2023-08-04
住户一楼被水淹 还冲来8头猪
 百态   2023-07-31
女子体内爬出大量瓜子状活虫
 百态   2023-07-25
地球连续35年收到神秘规律性信号,网友:不要回答!
 探索   2023-07-21
全球镓价格本周大涨27%
 探索   2023-07-09
钱都流向了那些不缺钱的人,苦都留给了能吃苦的人
 探索   2023-07-02
倩女手游刀客魅者强控制(强混乱强眩晕强睡眠)和对应控制抗性的关系
 百态   2020-08-20
美国5月9日最新疫情:美国确诊人数突破131万
 百态   2020-05-09
荷兰政府宣布将集体辞职
 干货   2020-04-30
倩女幽魂手游师徒任务情义春秋猜成语答案逍遥观:鹏程万里
 干货   2019-11-12
倩女幽魂手游师徒任务情义春秋猜成语答案神机营:射石饮羽
 干货   2019-11-12
倩女幽魂手游师徒任务情义春秋猜成语答案昆仑山:拔刀相助
 干货   2019-11-12
倩女幽魂手游师徒任务情义春秋猜成语答案天工阁:鬼斧神工
 干货   2019-11-12
倩女幽魂手游师徒任务情义春秋猜成语答案丝路古道:单枪匹马
 干货   2019-11-12
倩女幽魂手游师徒任务情义春秋猜成语答案镇郊荒野:与虎谋皮
 干货   2019-11-12
倩女幽魂手游师徒任务情义春秋猜成语答案镇郊荒野:李代桃僵
 干货   2019-11-12
倩女幽魂手游师徒任务情义春秋猜成语答案镇郊荒野:指鹿为马
 干货   2019-11-12
倩女幽魂手游师徒任务情义春秋猜成语答案金陵:小鸟依人
 干货   2019-11-12
倩女幽魂手游师徒任务情义春秋猜成语答案金陵:千金买邻
 干货   2019-11-12
 
推荐阅读
 
 
 
>>返回首頁<<
 
靜靜地坐在廢墟上,四周的荒凉一望無際,忽然覺得,淒涼也很美
© 2005- 王朝網路 版權所有