参考
Java原生的序列化协议,Protobuf, Thrift, Hessian, Kryo等等,这里说的序列化协议专指Java的基于二进制的协议,不是基于XML, JSON这种格式的协议。在实际开发中考虑了很多点,也遇到一些问题,拿出来说说。 抛开这些协议不说,结合实际的需求,一个理想的序列化协议至少考虑4个方面: 性能 是否支持被序列化对象新旧版本的兼容性问题。这个需求在实际开发中经常遇到,比如发布了一个服务,有很多客户端使用。当服务需要修改,新 添加1个参数时,不可能要求所有客户端都更新,那样牵扯的面太大,所以要做到新旧版本的兼容 是否可以直接序列化对象,而不需要额外的辅助类,比如用IDL生成辅助的序列化类 是否可以支持跨语言使用。
序列化这件事说白了就是把一个对象变成一个二进制流,然后把二进制流再转化成对象的过程。前者好说,关键是后者,后者其实就是一个如何分帧(Frame)的问题,即从哪个字节开始读几个字节来还原成数据的问题。常见的分帧方式有: 加结束符,比如http协议 定长 消息头 消息,消息头可以包含长度,类型信息
需要考虑的问题 对于Java序列化来说,肯定是第三种方式,但是如何设计这个分帧方式又有很多实现。下面说说上述具体有哪些考虑和问题。 第一是序列化后的字节数大小。最优的序列化后的字节数大小肯定是只有数据的二进制流,这样没有任何多余的分帧信息。如果要做到在二进制流里不加任何分帧信息来反序列化二进制流,有两个关键点: 确定具体的分帧方式 肯定要有个地方存放这个分帧方式,并且是序列化方和反序列化方都能拿到。 我把这个双方约定分帧方式叫做契约。实际操作的时候只需要序列化方按照契约把对象的数据转成二进制流,反序列化方按照契约把二进制流转成对象数据。 如果二进制流里面不加任何的分帧信息,那么反序列化方只能按照字段的顺序来依次分帧。理解一下这句话,如果单纯拿到一个只有纯数据的二进制流,那么只能按照约定的顺序依次来读取,并且还得知道每个字段的长度,这样才能知道读取几个字节来还原数据。在这里把顺序本身作为一个隐形的契约,双方按照顺序来读写。一旦顺序错了,就有可能发生反序列化的错误。 如果我们要字节数大小尽量小,那么我们第一想到的是把分帧信息不放在二进制流中,我们很自然而然想到被序列化对象的Class对象是最自然的选择,而且它还包含了字段的信息,Class.getDeclaredFields()可以返回类的所有实例字段。如果getDeclaredFields()方法返回的字段在任意JVM上都是同样的顺序,那么我们岂不就是可以指依靠序列化反序列化双方拿到被序列化的Class对象,然后利用反射机制拿到字段信息就可以实现最优的序列化后字节数大小吗? 但是经过我的调研发现,利用反射技术Class.getDeclared()方法返回的字段数组是没有排序也没有特定顺序的,比如按照声明的顺序。