序列化

本文内容来自网络,下文有对应得网址

 

1,什么是序列化
把对象或者结构体从内存中变成可存储或传输的过程称之为序列化。序列化是将一个对象转换成字节流(byte[],或者叫字符串,2进制串)以达到将其长期保存在内存、数据库或文件中的处理过程。它的主要目的是保存对象的状态以便以后需要的时候使用。序列化后的内容如果是为了方便以后需要的时候使用,自然是二进制序列化较好。Soap,XML,json ,protobuf序列化更多的是为了交换数据用的。


2,几个相关的知识
结构体或者对象在内存中都是01存储的,数据在2台电脑之间的网络通信也是01通信的。无论什么数据类型,在网络中只有一种传输方式,就是二进制数据包。因为物理层是以二进制传输数据的,但依据什么规则将字符转换为二进制,这个不清楚,说不定就是ASCII。比如:
string A = "Hello World";
byte[] data = Encoding.ASCII.GetBytes(A);
不论什么数据类型,都对应了01,使用data在传输时也是0101的传输,只不过接收方知道这个0101是按一个字节8位的方式来读取,且编码是ASCLL。然后发送data。这种方式对应了Json,xml之类的方式,将数据描述成字符串的形式,然后按照某种字符编码编码成字节数组,然后再01传输。
为什么是字符串呢?因为字符串是一个字节数组,而字节是最小的单位,所以序列化成字节数组,即字符串。

JSON,XML,ProtoBuf序列化:是将对象的属性以键值对的形式组织成字符串(一个编码过程),显然体积会增大很多。而且解码后也不能直接还原回原来的对象。

二进制序列化:是将对象的内存映射抽取出来形成字符串,还原时只有一个重新分配内存的过程。还原后依然还是你原来的对象。

数据结构,对象与二进制串:不同的计算机语言中,数据结构,对象以及二进制串的表示方式并不相同。
数据结构和对象:对于类似Java这种完全面向对象的语言,工程师所操作的一切都是对象(Object),来自于类的实例化。而在C++这种半面向对象的语言中,数据结构和struct对应,对象和class对应。

二进制串:序列化所生成的二进制串指的是存储在内存中的一块数据。C++语言具有内存操作符,所以C++语言的字符串可以直接被传输层使用,因为其本质上就是以'\0'结尾的存储在内存中的二进制串。在Java语言里面,二进制串的概念容易和String混淆。实际上String 是Java的一等公民,是一种特殊对象(Object)。对于跨语言间的通讯,序列化后的数据当然不能是某种语言的特殊数据类型。二进制串在Java里面所指的是byte[],byte是Java的8中原生数据类型之一。(猜想:使用json应该是将内存中的2进制串数据进行一次特定格式的字符串转换)。



3,序列化协议
互联网需要机器间通讯,而通讯需要共同的规则,约定的协议,序列化和反序列化属于通讯协议的一部分。比如:在OSI七层协议模型中展现层的主要功能是把应用层的对象转换成一段连续的二进制串,或者反过来,把二进制串转换成应用层的对象,这两个功能就是序列化和反序列化。
序列化和反序列化的选型是系统设计或重构一个重要的环节,在分布式、大数据量系统设计里面更为显著。恰当的序列化协议不仅可以提高系统的通用性、强健性、安全性、优化系统性能,而且会让系统更加易于调试、便于扩展。
每种序列化协议都有优点和缺点,它们在设计之初有自己独特的应用场景。在系统设计的过程中,需要考虑序列化需求的方方面面,综合对比各种序列化协议的特性,最终给出一个折衷的方案。

典型的序列化和反序列化过程往往需要如下组件:

IDL(Interface description language)文件:参与通讯的各方需要对通讯的内容需要做相关的约定(Specifications)。为了建立一个与语言和平台无关的约定,这个约定需要采用与具体开发语言、平台无关的语言来进行描述。这种语言被称为接口描述语言(IDL),采用IDL撰写的协议约定称之为IDL文件。
IDL Compiler:IDL文件中约定的内容为了在各语言和平台可见,需要有一个编译器,将IDL文件转换成各语言对应的动态库。Client/Server:指的是应用层程序代码,他们面对的是IDL所生存的特定语言的class或struct。
底层协议栈和互联网:序列化之后的数据通过底层的传输层、网络层、链路层以及物理层协议转换成数字信号在互联网中传递。

其他一些对比在该篇文章写的很清楚,非常好的文章:
https://tech.meituan.com/serialization_vs_deserialization.html?utm_source=tuicool?utm_source=tuicool 序列化和反序列化
内容包括:强健性,可调试性/可读性,性能,可扩展性/兼容性,常见的序列化和反序列化协议,包括XML、JSON、Protobuf、Thrift和Avro。
重点如下:
性能,性能包括两个方面,时间复杂度和空间复杂度:

第一、空间开销(Verbosity), 序列化需要在原有的数据上加上描述字段,以为反序列化解析之用。如果序列化过程引入的额外开销过高,可能会导致过大的网络,磁盘等各方面的压力。对于海量分布式存储系统,数据量往往以TB为单位,巨大的的额外空间开销意味着高昂的成本。

第二、时间开销(Complexity),复杂的序列化协议会导致较长的解析时间,这可能会使得序列化和反序列化阶段成为整个系统的瓶颈。
如果序列化后的数据人眼可读,这将大大提高调试效率, XML和JSON就具有人眼可读的优点。
XML本质上是一种描述语言,并且具有自我描述(Self-describing)的属性,所以XML自身就被用于XML序列化的IDL。
IDL悖论:JSON起源于弱类型语言Javascript,
JSON实在是太简单了,或者说太像各种语言里面的类了,所以采用JSON进行序列化不需要IDL。对于自身支持json格式数据的弱类型语言,语言自身就具备操作JSON序列化后的数据的能力; 由于JSON在一些语言中的序列化和反序列化需要采用反射机制(下文有描述。比如:在java中解析json,当java收到字节数组后(猜想:要不保存成文件,要不放到内存中),进行反序列化,假设先拿到前面某段数据是类名,那就需要根据类名创建对象,然后为对象赋值,这时使用反射是很方便的),所以在性能要求为ms级别。
来自于的以下链接的研究表明:XML所产生序列化之后文件的大小接近JSON的两倍。
http://www.codeproject.com/Articles/604720/JSON-vs-XML-Some-hard-numbers-about-verbosity
Protobuf:序列化数据非常简洁,紧凑,与XML相比,其序列化之后的数据量约为1/3到1/10。解析速度非常快,比对应的XML快约20-100倍。

选型建议:
以上描述的五种序列化和反序列化协议都各自具有相应的特点,适用于不同的场景:

1、对于公司间的系统调用,如果性能要求在100ms以上的服务,基于XML的SOAP协议是一个值得考虑的方案。
2、基于Web browser的Ajax,以及Mobile app与服务端之间的通讯,JSON协议是首选。对于性能要求不太高,或者以动态类型语言为主,或者传输数据载荷很小的的运用场景,JSON也是非常不错的选择。
3、对于调试环境比较恶劣的场景,采用JSON或XML能够极大的提高调试效率,降低系统开发成本。
4、当对性能和简洁性有极高要求的场景,Protobuf,Thrift,Avro之间具有一定的竞争关系。
5、对于T级别的数据的持久化应用场景,Protobuf和Avro是首要选择。如果持久化后的数据存储在Hadoop子项目里,Avro会是更好的选择。
6、由于Avro的设计理念偏向于动态类型语言,对于动态语言为主的应用场景,Avro是更好的选择。
7、对于持久层非Hadoop项目,以静态类型语言为主的应用场景,Protobuf会更符合静态类型语言工程师的开发习惯。
8、如果需要提供一个完整的RPC解决方案,Thrift是一个好的选择。
9、如果序列化之后需要支持不同的传输层协议,或者需要跨防火墙访问的高性能场景,Protobuf可以优先考虑。

 


4,其他相关内容:
网络传输时可以直接发送一个结构体吗? https://zhidao.baidu.com/question/1898708648822958300.html
在网络通讯过程中,对于结构体其实方法挺简单,由于结构体对象在内存中分配的空间都是连续的,所以可以将整个结构体直接转化成字符串(2进制01)发送,到了接收方再将这个字符串(2进制01)还原成结构体就大功告成了。这个结构体在发送方与接收方都必须声明。因为结构体对象的内存区域连续,同时每个成员的区块大小都分配好了,当接收完自己的区块,其实自己的数据已经接收完成。比如C++的一个全是值类型的结构体,发送到客户端Lua解析时,如果知道第一个是int类型,那只需要读最开始的32位,就可以拿到服务器发回来的int值了。

在C#中,需要序列化的类需要用[Serializable]标记。若某个字段不需要序列化,可标记为[NonSerialized],以节省网络传输的带宽以及减少序列化后文件大小,不能序列化属性(猜想:是因为2进制拿到的是内存映射,而内存映射中没有属性)。二进制序列化后得到的是一个二进制文件,而JSON序列化后得到的是JSON字符串。[Serializable()]操作是通过SerializableAttribute属性来实现的。类型中由SerializableAttribute标记的所有公共和私有字段都会进行序列化,除非该类型实现ISerializable接口来重写序列化进程(通过实现该接口我们便可以实现"自定义序列化")。如果可序列化类型的字段包含指针、句柄或其他某些针对于特定环境的数据结构,并且不能在不同的环境中以有意义的方式重建,则最好将NonSerializedAttribute属性应用于该字段。

对象保存到文件或数据库,网络编程时对象跨平台跨语言传输,即从windows上序列化的对象可到linux上反序列化,用c#序列化的对象可以被java反序列化。二进制序列化器BinaryFormatter,主要将对象序列化成流的形式,主要用于数据存储。JSON,XML,ProtoBuf序列化器,主要将对象序列化成字符串,用于数据传输。

在实际的应用中,二进制格式器往往应用于一般的桌面程序和网络通讯程序中,而XML格式器禀承了XML技术的优点,大多数被应用于.Net Remoting和XML Web服务等领域。

java反射解析Json:基本思路是为需要解析成的类定义好属性和属性的set方法,属性名需要和要解析的json数据键对应,属性类型和json数据的值类型对应,用List对应json数组,然后用反射机制遍历类的属性,根据属性名称去获取json对应的值并赋值。

 

 

5,其他不太相关的内容:
序列化二进制的话,存入到数据库之后,很难再查询了啊。 SQL语句也没办法查出你想找的对象。所以建议是拆分成多个表,然后关联起来。
将二进制序列化后的串写入文件,就可以认为他是数据库中的一个表(专用的而已)如果将你的对象细分成若干个子对象,分别对各子对象做二进制序列化,并以某种格式保存在同一个文件中。再配上管理方法,那么你就已经实现了自己的 NOSQL 了。

 


我的理解:
序列化本身是一种协议,同时也可以作为某种协议的一部分而存在。序列化有2种方式,2进制或者字符串(json),都是内存中拿到对象或者结构体的01数据,2进制方式在拿到之后直接转换成了2进制字符串,然后传输交流。而字符串方式(json)是在拿到01数据之后,按照特定的格式转换成了2进制字符串,然后传输交流。在解析的时候,2进制方式:读到2进制字符串,然后bit位去取数据,比如前32位就是一个int类型。字符串方式(json):读到2进制字符串,然后按照特定的格式拿到数据,再转化成当前语言的格式,比如:Lua的数据结构和C++完全不同,但是基本数据类型是一样的,所以必须把lua的数据结构转化成C++的数据结构来存储,再比如有些语言不能操作指针,如果传递的数据包含指针,那就完全没有意义,因为无法转化成该语言的格式。

还有一些特殊情况,比如:
如果2种语言都支持一样的结构体,如果结构体里的数据都是基本的值类型,使用2进制的方式,在取数据时,只需要按bit位取数据就行了。
在按字符串方式(json)方式时,如果特定的格式可以直接用当前的语言描述出来,那将省去序列化或者反序列化的步骤,可能需要使用内置的一些函数就行。比如:JavaScript默认就支持json格式的数据结构,将该对象直接转换成2进制字符串然后传输。JavaScript反序列时,读取到2进制字符串,然后调用相关函数转换成数据结构就行。

当然,序列化反序列一般都是使用IDL Compiler来进行的,按照规定的IDL格式来描述。比如:C#写了一个类的对象,将对象经过Json的编译器编译成Json格式,再讲Json格式转换成2进制发送出去。C++拿到2进制数据后,经过Json的编译器转换成Json格式,再将Json格式转换成C++的结构体。由此来看,对于像JavaScript,lua这类弱类型语言,直接就符合Json格式,所以不需要其中一步转换,或者很容易转换,效率会更高。

 

代码:

C#2进制序列化:
 

public class SerializableTest : MonoBehaviour {
    void Start () {
        int[] tmpInt = { 1, 2, 3 };
        byte[] bytes = SerializableObject(tmpInt);
        object obj = DeSerializbaleObject(bytes);
        for (int i = 0; i < ((int[])obj).Length; i++)
        {
            print(((int[])obj)[i]);
        }
    }
    public static byte[] SerializableObject(object obj)
    {
        if (obj == null)
            return null;
        MemoryStream ms = new MemoryStream();
        BinaryFormatter bf = new BinaryFormatter();
        bf.Serialize(ms, obj);
        byte[] bytetmp = ms.GetBuffer();
        ms.Close();
        return bytetmp;
    }

    public static object DeSerializbaleObject(byte[] bytes)
    {
        if (bytes == null)
            return null;
        MemoryStream ms = new MemoryStream(bytes);
        ms.Position = 0;
        BinaryFormatter bf = new BinaryFormatter();
        object obj = bf.Deserialize(ms);
        ms.Close();
        return obj;
    }
}

 

 

 

 

 

 

 

 

 

 

  • 6
    点赞
  • 12
    收藏
    觉得还不错? 一键收藏
  • 1
    评论

“相关推荐”对你有帮助么?

  • 非常没帮助
  • 没帮助
  • 一般
  • 有帮助
  • 非常有帮助
提交
评论 1
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值