分布式通信协议-序列化(分布式笔记)

Java序列化机制Serializable接口

Java的序列化机制存在的问题
1、序列化数据结果比较大、传输效率比较低
2、不能跨语言对接
影响:以至于在后来的很长一段时间,基于XML格式编码的对象序列化机制成为了主流,一方面解决了多语言兼容问题,另一方面比二进制的序列化方式更容易理解。以至于基于XML的SOAP协议及对应的WebService框架在很长一段时间内成为各个主流开发语言的必备的技术。
再到后来,基于JSON的简单文本格式编码的HTTP REST接口又基本上取代了复杂的Web Service接口,成为分布式架构中远程通信的首要选择。但是JSON序列化存储占用的空间大、性能低等问题,同时移动客户端应用需要更高效的传输数据来提升用户体验。在这种情况下与语言无关并且高效的二进制编码协议就成为了大家追求的热点技术之一。首先诞生的一个开源的二进制序列化框架-MessagePack。它比google的Protocol Buffers出现得还要早。
合适的序列化协议不仅可以提高系统的通用性、强壮型、安全性、优化性能。同时还能让系统更加易于调试和扩展。

REST和RESTful

Representational State Transfer,表述性状态转移是一组架构约束条件和原则,需要注意的是,REST是设计风格而不是标准,使用REST风格设计的应用程序就是RESTful。REST通常基于使用HTTP,URI,和XML(标准通用标记语言下的一个子集)以及HTML(标准通用标记语言下的一个应用)这些现有的广泛流行的协议和标准。
用URL定位资源,用HTTP动词(GET,POST,DELETE,DETC)描述操作。在设计web接口的时候,REST主要是用于定义接口名,接口名一般是用名词写,不用动词,那怎么表达“获取”或者“删除”或者“更新”这样的操作呢——用请求类型来区分。比如,我们有一个friends接口,对于“朋友”我们有增删改查四种操作,怎么定义REST风格的接口?
增加一个朋友,uri: generalcode.cn/va/friends 接口类型:POST
删除一个朋友,uri: generalcode.cn/va/friends 接口类型:DELETE
修改一个朋友,uri: generalcode.cn/va/friends 接口类型:PUT
查找朋友, uri: generalcode.cn/va/friends 接口类型:GET
上面我们定义的四个接口就是符合REST协议的,请注意,这几个接口都没有动词,只有名词friends,都是通过Http请求的接口类型来判断是什么业务操作。举个反例:generalcode.cn/va/deleteFriends 该接口用来表示删除朋友,这就是不符合REST协议的接口。一般接口的返回值是JSON或者XML类型的,互联网项目一般都是JSON类型的。
用HTTP Status Code传递Server的状态信息。比如最常用的 200 表示成功,500 表示Server内部错误,403表示Bad Request等。(反例:传统web开发返回的状态码一律都是200,其实不可取。)那这种风格的接口有什么好处呢?前后端分离。前端拿到数据只负责展示和渲染,不对数据做任何处理。后端处理数据并以JSON格式传输出去,定义这样一套统一的接口,在web,ios,android三端都可以用相同的接口,是不是很爽?!

序列化和反序列化

1. 什么是序列化?反序列化?
Java中对象的序列化指的是将对象转换成以字节序列的形式来表示,这些字节序列包含了对象的数据和信息,一个序列化后的对象可以被写到数据库或文件中,也可用于网络传输,一般当我们使用缓存cache(内存空间不够有可能会本地存储到硬盘)或远程调用rpc(网络传输)的时候,经常需要让我们的实体类实现Serializable接口,目的就是为了让其可序列化,然后使用 ObjectInputStream 和 ObjectOutputStream 进行对象的读写。当然,序列化后的最终目的是为了反序列化,恢复成原先的Java对象
seriallization 序列化:将对象转化为便于传输的格式, 常见的序列化格式:二进制格式,字节数组,json字符串,xml字符串。deseriallization 反序列化:将序列化的数据恢复为对象的过程。
比如:现在我们都会在淘宝上买桌子,桌子这种很不规则不东西,该怎么从一个城市运输到另一个城市,这时候一般都会把它拆掉成板子,再装到箱子里面,就可以快递寄出去了,这个过程就类似我们的序列化的过程(把数据转化为可以存储或者传输的形式)。当买家收到货后,就需要自己把这些板子组装成桌子的样子,这个过程就像反序列 的过程(转化成当初的数据对象)。
2、为什么要序列化?
我们都知道,在进行浏览器访问的时候,我们看到的文本、图片、音频、视频等都是通过二进制序列进行传输的,那么如果我们需要将Java对象进行传输的时候,是不是也应该先将对象进行序列化?答案是肯定的,我们需要先将Java对象进行序列化,然后通过网络,IO进行传输,当到达目的地之后,再进行反序列化获取到我们想要的对象,最后完成通信。
3、怎么去实现一个序列化操作
1、实现Serializable接口
2、ObjectInputStream:读取指定的字节数据转换成对象
3、ObjectOutputStream:将指定对象转换成字节数据
4、serialVersionUID的作用
文件流中的class和classpath中的class,也就是修改过后的class,不兼容了,处于安全机制考虑,程序抛出了错误,并且拒绝载入。从错误结果来看,如果没有为指定的class配置serialVersionUID,那么java编译器会自动给这个class进行一个摘要算法,类似于指纹算法,只要这个文件有任何改动,得到的UID就会截然不同的,而为指定的class配置serialVersionUID可以保证在这么多类中,这个编号是唯一的。所以,由于没有事先指定 serialVersionUID,编译器又为我们生成了一个UID,当然和前面保存在文件中的那个不会一样了,于是就出现了2个序列化版本号不一致的错误。因此,只要我们自己指定了serialVersionUID,就可以在序列化后,去添加一个字段,或者方法,而不会影响到后期的还原,还原后的对象照样可以使用,而且还多了方法或者属性可以用。
5、静态变量的序列化
序列化并不保存静态变量的值,所以,序列化->修改静态变量和成员变量的值->反序列化->访问反序列 对象的静态变量和成员变量 静态变量的值是修改后的 成员变量的值是修改前的
6、Transient关键字
transient关键字的作用,让某些被修饰的成员属性变量不会被序列化。
Transient关键字的使用时机:类中的字段值可以根据其它字段推导出来,如一个长方形类有三个属性:长度、宽度、面积(示例而已,一般不会这样设计),那么在序列化的时候,面积这个属性就没必要被序列化了。
为什么使用transient:主要是为了节省存储空间,比如类的成员变量中有一些冗余的大对象,其它的感觉没啥好处,可能还有坏处(有些字段可能需要重新计算,初始化什么的),总的来说,利大于弊。
7、父子类问题
如果父类没有实现序列化,而子类实现列序列化。那么子类从父类继承的成员将无法序列化。
8、序列化的存储规则

   ObjectOutputStream out = new ObjectOutputStream(new FileOutputStream("result.obj"));
   Test test = new Test();
   //试图将对象两次写入文件
   out.writeObject(test);
   out.flush();
   System.out.println(new File("result.obj").length());
   out.writeObject(test);
   out.close();
   System.out.println(new File("result.obj").length());
 
   ObjectInputStream oin = new ObjectInputStream(new FileInputStream("result.obj"));
   //从文件依次读出两个文件
   Test t1 = (Test) oin.readObject();
   Test t2 = (Test) oin.readObject();
   oin.close();
            
   //判断两个引用是否指向同一个对象
   System.out.println(t1 == t2);

代码中对同一对象两次写入文件,打印出写入一次对象后的存储大小和写入两次后的存储大小,然后从文件中反序列化出两个对象,比较这两个对象是否为同一对象。一 般的思维是,两次写入对象,文件大小会变为两倍的大小,反序列化时,由于从文件读取,生成了两个对象,判断相等时应该是输入 false 才对,但是最后结果输出如下图所示。
在这里插入图片描述
Java 序列化机制为了节省磁盘空间,具有特定的存储规则,当写入文件的为同一对象时,并不会再将对象的内容进行存储,而只是再次存储一份引用,上面增加的 5 字节的存储空间就是新增引用和一些控制信息的空间。反序列化时,恢复引用关系,使得代码 中的 t1 和 t2 指向唯一的对象,二者相等,输出 true。该存储规则极大的节省了存储空间。

序列化的实际应用

1、 什么是深度克隆、浅度克隆?如何用序列化实现深度克隆?
Object中的克隆方法是浅度克隆,JDK规定了克隆需要满足的一些条件,简要总结一下就是:对某个对象进行克隆,对象的的成员变量如果包括引用类型或者数组,那么克隆的时候其实是不会把这些对象也带着复制到克隆出来的对象里面的,只是复制一个引用,这个引用指向被克隆对象的成员对象,但是基本数据类型是会跟着被带到克隆对象里面去的。而深度可能就是把对象的所有属性都统统复制一份新的到目标对象里面去。简单画个图:
在这里插入图片描述

public class CloneDemo {
    public static void main(String[] args) throws IOException, ClassNotFoundException {
        Teacher teacher=new Teacher();
        teacher.setName("mic");
        Student student=new Student();
        student.setName("沐风");
        student.setAge(35);
        student.setTeacher(teacher);
        Student student2=(Student) student.deepClone(); //克隆一个对象
        System.out.println(student);
        student2.getTeacher().setName("james");
        System.out.println(student2);
    }
}

public class Student implements Serializable{
    private static final long serialVersionUID = 5630895052908986144L;
    private String name;
    private int age;
    private Teacher teacher;
    public Object deepClone() throws IOException, ClassNotFoundException {
        //序列化
        ByteArrayOutputStream baos=new ByteArrayOutputStream();
        ObjectOutputStream oos=new ObjectOutputStream(baos);
        oos.writeObject(this);
        //反序列化
        ByteArrayInputStream bais=new ByteArrayInputStream(baos.toByteArray());
        ObjectInputStream ois=new ObjectInputStream(bais);
        return ois.readObject();
    }
    @Override
    public String toString() {
        return "Student{" +
                "name='" + name + '\'' +
                ", age=" + age +
                ", teacher=" + teacher +
                '}';
    }
    public String getName() {
        return name;
    }
    public void setName(String name) {
        this.name = name;
    }
    public int getAge() {
        return age;
    }
    public void setAge(int age) {
        this.age = age;
    }
    public Teacher getTeacher() {
        return teacher;
    }
    public void setTeacher(Teacher teacher) {
        this.teacher = teacher;
}
}

public class Teacher implements Serializable{
    private static final long serialVersionUID = -6635991328204468281L;
    private String name;
    public String getName() {
        return name;
    }
    public void setName(String name) {
        this.name = name;
    }
    @Override
    public String toString() {
        return "Teacher{" +
                "name='" + name + '\'' +
                '}';
    }
}

总结

1、在java中,只要一个类实现了java.io.Serializable接口,那么它就可以被序列化
2、通过ObjectOutputStream和ObjectInputStream将对象进行序列化和反序列化
3、对象是否允许被反序列化,不仅仅是取决于对象的代码是否一致,同时还有一个重要的因素(UID)
4、静态变量不会被序列化
5、要将从父类继承的属性序列化,那么父类也必须实现Serializable接口
6、transient关键字,主要是控制变量是否能够被序列化。没有被序列化的(transient)成员变量反序列化后,会被设置成初始值,比如String -> null
7、通过序列化操作可实现深度克隆

主流的序列化技术有哪些

使用JAVA进行序列化有它的优点,也有它的缺点。
优点:JAVA语言本身提供,使用比较方便和简单。
缺点:不支持跨语言处理,性能相对不是很好,序列化以后产生的数据相对较大。
XML序列化框架
XML序列化的好处在于可读性好,方便阅读和调试。但是序列化以后的字节码文件比较大,而且效率不高,适用于对性能不高,而且QPS较低的企业级内部系统之间的数据交换的场景,同时XML又具有语言无关性,所以还可以用于异构系统之间的数据交换和协议。比如我们熟知的Webservice,就是采用XML格式对数据进行序列化的。
JSON序列化框架
JSON(JavaScript Object Notation)是一种轻量级的数据交换格式,相对于XML来说,JSON 的字节流更小,而且可读性也非常好。现在JSON数据格式在企业运用是最普遍的,JSON序列化常用的开源工具有很多。

  1. Jackson (https://github.com/FasterXML/jackson)
  2. 阿里开源的 FastJson (https://github.com/alibaba/fastjon)
  3. Google的GSON (https://github.com/google/gson)
    这几种json序列化工具中,Jackson与fastjson要比GSON的性能要好,但是Jackson、GSON的稳定性要比Fastjson好,而fastjson的优势在于提供的api非常容易使用。

Hessian序列化框架
Hessian是一个支持跨语言传输的二进制序列化协议,相对于Java默认的序列化机制来说,Hessian具有更好的性能和易用性,而且支持多种不同的语言。实际上Dubbo采用的就是Hessian序列化来实现,只不过Dubbo对Hessian进行了重构,性能更高。
Protobuf 序列化框架
Protobuf是Google的一种数据交换格式,它独立于语言、独立于平台。
Google提供了多种语言来实现,比如Java、C、Go、Python,每一种实现都包含了相应语言的编译器和库文件。Protobuf使用比较广泛,主要是空间开销小和性能比较好,非常适合用于公司内部对性能要求高的RPC调用。 另外由于解析性能比较高,序列化以后数据量相对较少,所以也可以应用在对象的持久化场景中。但是要使用Protobuf会相对来说麻烦些,因为他有自己的语法,有自己的编译器。
百度有提供一个自己封装的 且便于使用的Protobuf。

<dependency>
    <groupId>com.baidu</groupId>
    <artifactId>jprotobuf</artifactId>
    <version>2.1.2</version>
</dependency>
  • 0
    点赞
  • 0
    收藏
    觉得还不错? 一键收藏
  • 0
    评论

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

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

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

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

抵扣说明:

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

余额充值