序列化和反序列化之学习笔记

1.java领域的对象传输

1.基于socket对象传输

1.传输对象

package com.study.serializable;

/**
 * Created by chenli on 2019/11/9.
 */
public class User {
    private String name;
    private int age;

    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;
    }

    @Override
    public String toString() {
        return "User{" +
                "name='" + name + '\'' +
                ", age=" + age +
                '}';
    }
}

2.ClientSocket–客户端

package com.study.serializable;

import java.io.IOException;
import java.io.ObjectOutputStream;
import java.net.Socket;

/**
 * Created by chenli on 2019/11/9.
 */
public class ClientSocket {
    public static void main(String[] args) throws IOException {
        Socket socket=new Socket("localhost",8080);
        ObjectOutputStream objectOutputStream=new ObjectOutputStream(socket.getOutputStream());

        User user=new User();
        user.setAge(12);
        user.setName("chenli");
        objectOutputStream.writeObject(user);
        objectOutputStream.flush();
    }
}

3.SocketService服务端

package com.study.serializable;

import java.io.IOException;
import java.io.ObjectInputStream;
import java.net.ServerSocket;
import java.net.Socket;

/**
 * Created by chenli on 2019/11/9.
 */
public class SocketService {
    public static void main(String[] args) throws IOException, ClassNotFoundException {
        ServerSocket serverSocket=null;
        serverSocket=new ServerSocket(8080);

       Socket socket= serverSocket.accept();

        ObjectInputStream objectInputStream=new ObjectInputStream(socket.getInputStream());

        User user=(User) objectInputStream.readObject();
        System.out.println("server:接收到的user:"+user);
    }
}

由于对象未序列话,则报出的错误

Exception in thread "main" java.io.NotSerializableException: com.study.serializable.User
	at java.io.ObjectOutputStream.writeObject0(ObjectOutputStream.java:1184)
	at java.io.ObjectOutputStream.writeObject(ObjectOutputStream.java:348)
	at com.study.serializable.ClientSocket.main(ClientSocket.java:18)

表示该对象未序列话,解决错误的方法是给user实现Serializable序列话
接受数据成功。

1.2 序列化的意义

给User对象加上Serializable接口 就可以实现远程传输;
序列话是把对象的状态信息转化为可储存或者传输的形式过程。把对象转化为字节序列的过程称为序列化。
反序列话是把字节组反序列化恢复为对象的过程为反序列化。

1.3序列化的高阶认识

java原生序列化
主 要通过输出流java.io.ObjectOutputStream和对象输入流java.io.ObjectInputStream来实现。
java.io.ObjectOutputStream:表示对象输出流 , 它的writeObject(Object obj)方法可以对参 数指定的obj对象进行序列化,把得到的字节序列写到一个目标输出流中。 java.io.ObjectInputStream:表示对象输入流 ,它的readObject()方法源输入流中读取字节序 列,再把它们反序列化成为一个对象,并将其返回。需要注意的是被序列化的对象要实现Serializable接口。

1.3.1serialVersionUID 的作用

序列化版本号,凡是实现Serializable接口的类都有一个表示序列化版本标识 符的静态变量

  1. 先将user对象序列化到文件中
  2. 然后修改user对象,增加serialVersionUID字段
  3. 然后通过反序列化来把对象提取出来
  4. 演示预期结果:提示无法反序列化
    结论:java的序列化机制通过验证serialVersionUID的一致性。JVM 会把传来的字节流中的 serialVersionUID 与本地相应实体类的 serialVersionUID 进 行比较,如果相同就认为是一致的,可以进行反序列化,否则就会出现序列化版本不一致的 异常,即是InvalidCastException。
    从结果可以看出,文件流中的 class 和 classpath 中的 class,也就是修改过后的 class,不兼 容了,处于安全机制考虑,程序抛出了错误,并且拒绝载入。从错误结果来看,如果没有为 指定的class配置serialVersionUID,那么java编译器会自动给这个class进行一个摘要算法, 类似于指纹算法,只要这个文件有任何改动,得到的UID就会截然不同的,可以保证在这么 多类中,这个编号是唯一的。所以,由于没有显指定 serialVersionUID,编译器又为我们生成了一个 UID,当然和前面保存在文件中的那个不会一样了,于是就出现了 2 个序列化版本号 不一致的错误。因此,只要我们自己指定了serialVersionUID,就可以在序列化后,去添加一 个字段,或者方法,而不会影响到后期的还原,还原后的对象照样可以使用,而且还多了方 法或者属性可以用。

1.3.2 tips:serialVersionUID 有两种显示的生成方式:

一是默认的 1L ,比如: private static final long serialVersionUID = 1L; 二是根据类名、接口名、成员方法及属性等来生成一个 64 位的哈希字段 ;
当序列化接口没有显示的定义这个属性时,则java序列化机制会自动根据编译的 Class 自动生成一个 serialVersionUID 作 序 列 化 版 本 比 较 用 ,这 种 情 况 下,如果 Class 文件 ( 类名,方法明等 ) 没有发生变化 ( 增加空格,换行,增加注释等等 ) ,就算 再编译多次, serialVersionUID 也不会变化的。

1.3.3Transient 关键字

Transient 是控制变量序列化,在属性上加上这个关键字,则不能被序列化,在被反序列化后,transient 变量的值被设为初始值,如 int 型的是 0,对象型的是 null。
虽然name被transient修饰,但是通过我们写的这两个方法依然能够使得name字段正确 被序列化和反序列化

1.3.4 writeObject 和 readObject 原理

writeObject 和 readObject 是两个私有的方法,他们是什么时候被调用的呢?从运行结果来 看,它确实被调用。而且他们并不存在于Java.lang.Object,也没有在Serializable中去声明。 我们唯一的猜想应该还是和 ObjectInputStream 和 ObjectOutputStream 有关系,所以基于 这个入口去看看在哪个地方有调用
序列化和反序列化之学习笔记_第1张图片
从源码层面来分析可以看到,readObject是通过反射来调用的。 其实我们可以在很多地方看到readObject和writeObject的使用,比如HashMap。

1.4 Java 序列化的一些简单总结

1.java序列化只针对对象的状态保存,对象的方法,序列化不关心
2.当一个父类实现了序列化,它的子类会自动实现序列化,不需要显示到接口上。
3.当一个对象的实例变量引用的其他对象,序列化这个对象的时候会自动把引用的对象序列化。
4.当某个字段被申明为transient后,默认的序列化机制会忽略这个字段
5. 被申明为 transient 的字段,如果需要序列化,可以添加两个私有方法:writeObject 和 readObject ;

2.分布式架构下的序列化

2.1 序列化的发展

随着分布式架构、微服务架构的普及。服务与服务之间的通信成了最基本的需求。这个时候, 我们不仅需要考虑通信的性能,也需要考虑到语言多元化问题 所以,对于序列化来说,如何去提升序列化性能以及解决跨语言问题,就成了一个重点考虑 的问题。
由于Java本身提供的序列化机制存在两个问题:
1.序列化数据比较大,传输效率低
2.其他语言无法实现对接

2.2了解各种序列化技术

1.xml序列化的可读性好,方便阅读和调试;字节码文件比价大,不方便阅读;
2. JSON序列化
Jackson /FastJson /Google的GSON ;Jackson 与 fastjson 要比 GSON 的性能要好,但是 Jackson、 GSON的稳定性要比Fastjson好。而fastjson的优势在于提供的api非常容易使用 ;
3.Hessian 序列化
Hessian 是一个支持二进制序列化协议,Hessian 具有更好的性能和易用性;支持不同的语言;实际上dubbo采用的是Hessian 序列化来实现,只不过dubbo对Hessian 进行重构,性能高;
4.Avro 序列化
支持 二进制序列化方式,可以便捷,快速地处理大量数据;
5.kyro 序列化框架
kyro 是一种非常成熟的序列化实现,已经在Hive、Storm)中使用得比较广泛,不过它不能 跨语言. 目前 dubbo 已经在 2.6 版本支持 kyro 的序列化机制。它的性能要优于之前的 hessian2
6.Protobuf 序列化
Protobuf是Google的一种数据交换格式,它独立于语言、独立于平Protobuf使用比较广泛,主要是空间开销小和性能比较好,非常适合用于公司内部对性能要 求高的 RPC 调用。 另外由于解析性能比较高,序列化以后数据量相对较少,所以也可以应 用在对象的持久化场景中 。

3.序列化技术选型

  • 技术层面
  1. 序列化空间开销,也就是序列化产生的结果大小,这个影响到传输的性能
  2. 序列化过程中消耗的时长,序列化消耗时间过长影响到业务的响应时
  3. 序列化协议是否支持跨平台,跨语言。因为现在的架构更加灵活,如果存在异构系统通信 需求,那么这个是必须要考虑的
  4. 可扩展性/兼容性,在实际业务开发中,系统往往需要随着需求的快速迭代来实现快速更新, 这就要求我们采用的序列化协议基于良好的可扩展性/兼容性,比如在现有的序列化数据结 构中新增一个业务字段,不会影响到现有的服务技术的流行程度,越流行的技术意味着使用的公司多,那么很多坑都已经淌过并且得到了解决,技术解决方案也相对成熟
  • 选型建议
  1. 对性能要求不高的场景,可以采用基于XML的SOAP协议
  2. 对性能和间接性有比较高要求的场景,那么Hessian、Protobuf、Thrift、Avro都可以。
  3. 基于前后端分离,或者独立的对外的 api 服务,选用 JSON 是比较好的,对于调试、可读 性都很不错
  4. Avro设计理念偏于动态类型语言,那么这类的场景使用Avro是可以的
    各个序列化技术的性能比较

你可能感兴趣的:(序列化和反序列化之学习笔记)