C# 中的序列化与反序列化(.NET icode9源码学习)
作者:互联网
假如有一天我们要在在淘宝上买桌子,桌子这种很不规则不东西,该怎么从一个城市运输到另一个城市,这时候一般都会把它拆掉成板子,再装到箱子里面,就可以快递寄出去了。这个过程就类似我们的序列化的过程(把数据转化为可以存储或者传输的形式)。当买家收到货后,就需要自己把这些板子组装成桌子的样子,这个过程就像反序列的过程(转化成当初的数据对象)。
序列化是指将对象转换成字节流,从而存储对象或将对象传输到内存、数据库或文件的过程。 它的主要用途是保存对象的状态,以便能够在需要时重新创建对象。反向过程称为“反序列化”。有点类似于压缩与解压的过程。
【注:
(1) 文章篇幅较长,可直接转跳至想阅读的部分。
(2) 以下提到的复杂度仅为算法本身,不计入算法之外的部分(如,待排序数组的空间占用)且时间复杂度为平均时间复杂度。
(3) 除特殊标识外,测试环境与代码均为 .NET 6/C# 10。
(4) 默认情况下,所有解释与用例的目标数据均为升序。
(5) 默认情况下,图片与文字的关系:图片下方,是该幅图片的解释。
(6) 文末“ [ # … ] ”的部分仅作补充说明,非主题(算法)内容,该部分属于 .NET 底层运行逻辑,有兴趣可自行参阅。
(7) 本文内容基本为本人理解所得,可能存在较多错误,欢迎指出并提出意见,谢谢。】
【注:
1. 本文在此仅介绍序列化的使用方法及相关表层内容,碍于篇幅,源码分析将在之后的文章中进一步介绍】
2. 本文每一个分析过程间的联系性可能较低,建议先阅读总结部分,再阅读正文
3. 此篇文章内容较为复杂,篇幅较大建议分段阅读、先看总结再看内容】
一、序列化的作用与意义
先考虑压缩与解压。我们与一堆保存了信息的文件,现在需要将其通过网络发送给其他人。相信我们不会直接一个一个文件的传,而是将其放在一个文件夹或作为一个压缩包后在传递。这样,即节省了空间,又加快了传输,同时将其打包后也让我们在之后对这一堆文件有更好的管理。
- 传输。举个例子,一座大厦好比一个对象,现在计划要把这座大厦搬到另一个地方去,直接挪肯定不太现实。(一般地,网络传输只能通过字节流,不能直接传输对象)。因此我们就把大厦拆成每一块砖,给每块砖定一个编号,知道这是在大厦的哪一部分。在这个过程中序列化就起到了将大厦分成砖头的作用,方便数据的交互。
- 存储。在某些程序运行时会产生一些对象,这些对象随着程序的停止而消失,但如果我们想把某些对象保存下来,在程序终止运行后,继续让这些对象存在,可以使程序再次运行时读取这些对象的值,或在其他程序中利用这些保存下来的对象。我们将这个过程命名为序列化。最常见的:Ctrl C / X,Ctrl V。
这时候就又有一个问题:为什么要将其序列化后再读写而不直接对对象本身进行读写?
我们要将对象写入一个磁盘文件,再将其读出来,会产生什么问题?其中一个最大的问题就是对象引用。再举个例子,假设现在有两个类,A 与 B。B类中含有一个指向A类对象的引用,现在我们对两个类进行实例化 { A a = new A(); B b = new B(); },这时在内存中实际上分配了两个空间,一个存储对象a,一个存储对象b。接下来我们将它们写入到磁盘的一个文件中去,就在写入文件时出现了问题。因为对象b包含对于对象a的引用,所以系统会自动的将a的数据复制一份到b,这样的话当我们从文件中恢复对象时(也就是重新加载到内存中)时,内存分配了三个空间,而对象a同时在内存中存在两份【注意:此处的复制指的是文件的复制,并非程序运行时的浅层复制,因此对于 a 会产生新的两个无关对象】。此时,若想在文件上修改对象a的数据的话,就要搜索它的每一份拷贝来达到对象数据的一致性这样增加了不少负担。而序列化就解决了这样的问题。
序列化的机制:
(1)保存到磁盘的所有对象都获得一个序列号(1, 2, 3…)
(2)当要保存一个对象时,先检查该对象是否被保存了。
(3)如果以前保存过,只需写入与已经保存的具有序列号 k 的对象相同的标记;否则,保存该对象
利用编号的方法,解决了对象引用的问题,类似于程序设计中的复用。
小结,需要序列化的原因:
- 因为在网络传输时,一般只能使用数据流的形式,需要将对象转换为便于传输形式。
- 某些情况下需要保存一些对象的特定情况,供其他时候使用。
二、基本序列化方式及其效率
使用 BinaryFormatter 进行串行化的二进制形式序列化(必须添加 System.Runtime.Serialization.Formatters.Binary; 命名空间);
使用SOAP协议进行的序列化;
使用 XmlSerializer 进行串行化的XML形式序列化对象;
JSON 序列化。
【注:如果一个类所创建的对象,能够被序列化,那么要求必须给这个类加上 [Serializable] 特性】
(一) 二进制序列化
需要引入命名空
定义一个类,用于作为序列化的对
定义待处理对象
定义一下序列化与反序列化方法
【思考:为什么不能用 Line 46 行的语句?】
因为在类中,我们采用的是简便属性,且采用构造方法对字段直接赋值。而简便属性似乎无法返回直接通过字段赋值的字段值(此推论和本人之前的映像不太相符,欢迎各位学者提出观点)因此该对象的此属性值恒为 null。