序列化与反序列化

文摘   职场   2022-11-04 17:10   江苏  
  1. 为什么要序列化?

    众所周知,计算机底层存储或网络传输的数据都必须是二进制数据;所谓底层,就是最下面最接近硬件的部分,为了方便大家理解,我先整理出一张图供大家对应着看,如果有不对,欢迎指正:


    在Java这门语言中,一切皆对象,这是基于开发人员理解抽象出来的一种“思想”;对于底层设备来说对象是什么玩意儿跟他没关系,要想通过底层,就必须要尊重底层定义把数据转换二进制。


  2. Java中序列化方式

    2.1 实现Serializable接口—— 这种方式可能是比较多的同学在使用的,有的IDE在实现该接口后会提示开发人员要生成一个Long类型的标识,本人曾经遇到过的一个坑就是为了避免DB被过多的查询搞死,将查询结果放置的缓存,但定义的这个对象因新功能被其它同学修改过,上线后将缓存结果反序列化成对象时出错;还有的(低版本)RPC框架通信时如果一个对象没有实现该接口可能会报错;特别说明这种方式只会序列化所有非static和transient关键字修饰的成员变量。

    2.2 实现Externalizable接口—— 这种方式本人没用过;但查看源码得知有如下限制:实现该接口必须实现writeExternal()方法和readExternal()方法且必须有一个无参的构造函数,支持手动选择哪些部分序列化。


    2.3 实现Serializable接口,添加writeObject()和readObject()方法;该方式结合了2.1、2.2两种方式的优点,限制有 a:) 方法必须是private关键字修饰 b:) 第一行调用默认的defaultWriteObject() / defaultReadObject() 完成非static和transient修饰的变量的序列化与反序列化 c:) 调用writeObject() / readObject()方法对“指定的”成员变量进行序列化与反序列化;这种方式在源码ArrayList中可以看见


  3. 其它序列化方式

    alibaba的fastJson、fasterxml的jackson、facebook的Thrift、Google的Protobuf等等。其中前2个本人都用过引入相关的maven坐标就好,但要提醒的是使用者要注意关注相关的技术栈,时常有漏洞提示,为避免给公司造成损失,请按官方说明进行升级;thrift暂未接触,因本人当前所在公司属于汽车的智能辅助驾驶,考虑到车机客户端的情况,当前使用protobuf进行通信


  4. 认识Protobuf(Google Protocol Buffers)

    官方文档对 Protobuf的定义:protocol buffers 是一种语言无关、平台无关、可扩展的序列化结构数据的方法,可用于数据通信协议和数据存储等,它是 Google 提供的一个具有高效协议数据交换格式工具库,是一种灵活、高效和自动化机制的结构数据序列化方法。相比XML,有编码后体积更小,编、解码速度更快的优势;相比于 Json,Protobuf 有更高的转化效率,时间效率和空间效率都是 JSON 的 3-5 倍。

    优点:

    a. 性能好/效率高:无论时间、空间上都是比较低的

    b. 有代码生成机制:可以将.proto格式的文件转换成指定开发语言的文件

    c. 支持多种编程语言:C,C++,Java,Go,Dart,Python,Kotlin.....

    d. 支持向前或向后兼容:在不同的协议中对多出来的字段定义可以“忽略”或变成“可选

    缺点:

    a. 二进制格式文件导致可读性差

    b. 缺乏自释性描述:如xml的节点名称,json的key

    c.  通用性差:仍然是相对json和xml来的,对不同项目需要做适配工作


    举例说明

    因本人工作中的proto文件(业务)定义太过繁杂,为方便大家理解和遵守公司规章制度,我将通过一个简单的(通讯录)例子向给大家展示:


     //定义4个相关联的Java类 然后 初始化并且写文件@Datapublic class AddressBook implements Serializable {    private List<Person> people;    //通讯录}@Datapublic class Person implements Serializable {    private Integer id;     //标识    private String name;    //姓名    private String email;   //邮箱    private List<PhoneNumber> phones; //联系方式}@Datapublic class PhoneNumber implements Serializable {    private String number;    private PhoneType type;}public enum PhoneType {    MOBILE,HOME,WORK}


    //定义proto格式文件并且初始化syntax = "proto2";
    package your.package;
    //true代表 将嵌套的文件拆开生成在不同的当中 false:类似Java的嵌套类option java_multiple_files = true;//该选项可以没有:当未指定时,生成的文件 会自动放置在package目录下(没有则创建),反之则创建目录option java_package = "proto";//生成指定语言的文件名option java_outer_classname = "AddressBook";
    message Person { optional string name = 1; optional int32 id = 2; optional string email = 3;
    enum PhoneType { MOBILE = 0; HOME = 1; WORK = 2; }
    message PhoneNumber { optional string number = 1; optional PhoneType type = 2 [default = HOME]; }
    repeated PhoneNumber phones = 4;}
    message AddressBook { repeated Person people = 1;}



  5. 数据对比

    通过右键查看文件可以看到proto序列化后比Java原生序列化后占用空间一样,因为磁盘是以页为单位(默认4k),但实际大小上还是有区别的(该结果受操作系统和数据量影响),感兴趣的可以试一下



  6. 数据读取

    Java的序列化读取文件这里就不用举例了,本篇只为举例完成Proto的反序列化,能正确取出放进去的值即可。

    public static void main(String[] args) throws Exception {        proto.AddressBook read = proto.AddressBook.parseFrom(new FileInputStream(new File("d:/proto/address_book_proto.txt")));        for (proto.Person per : read.getPeopleList()) {            System.out.println("id:"+per.getId());            System.out.println("name:"+per.getName());            System.out.println("email:"+per.getEmail());            for (proto.Person.PhoneNumber phoneNumber : per.getPhonesList()) {                System.out.println("type:"+phoneNumber.getType());                System.out.println("number:"+phoneNumber.getNumber());            }        }    }



  7. 如何生成Java类

    a. 通过命令行

    protoc -I=$src_dir --java_out=$dst_dir  $src_dir/address_boo.proto

    $src_dir:protoc可执行文件所在目录,如果没有则为当前目录

    $dst_dir:生成Java文件存放的目录,记得带文件名,支持多目录

    更多参数选项可通过protoc help查看

    b. 通过插件

    如果你用的是idea可安装插件后通过:Tools -> Configure GenProtoBuf完成配置后直接生成相应的文件


  8. 写在最后

  9. 文章只是我在某车企这段工作中的一点小收获,为避免遗忘特记录在此。不代表适合任何公司业务,就像前面所述,当前业务是跑在车机上,相对于PC来说,用户车端网络、存储等资源是比较昂贵的,所以我们选择了Proto协议。

    其实通过proto生成的Java文件还有很多值得去讲解,比如使用了builder模式、生成了更多的方法方便各种场景,这些在打.(点)的时候通过提示就可以看到,有兴趣可以试一试。

  10. 参考链接(微信不支持外链,辛苦copy一下了)

    proto可执行文件下载地址:https://developers.google.com/protocol-buffers/docs/downloads

    性能对比:https://github.com/eishay/jvm-serializers/wiki

    学习文档:https://developers.google.com/protocol-buffers

晚霞程序员
一位需要不断学习的30+程序员……