美文网首页
(12)分布式系统的基石序列化和反序列化

(12)分布式系统的基石序列化和反序列化

作者: Mrsunup | 来源:发表于2018-11-11 21:31 被阅读0次

    1. 了解序列化的意义

    Java 平台允许我们在内存中创建可复用的 Java 对象,但一般情况下,只有当JVM处于运行时,这些对象才可能存在, 即,这些对象的生命周期不会比 JVM 的生命周期更长。但在现实应用中,就可能要求在 JVM停止运行之后能够保存(持久化)指定的对象,并在将来重新读取被保存的对象。Java对象序列化就能够帮助我们实现该功能

    序列化:是把对象的状态信息转化为可存储或传输的形式过程,也就是把对象转化为字节序列的过程称为对象的序列化

    反序列化:是序列化的逆向过程,把字节数组反序列化为对象,把字节序列恢复为对象的过程成为对象的反序列化

    2.序列化面临的挑战

    评价一个序列化算法优劣的两个重要指标是:
    序列化以后的数据大小
    序列化操作本身的速度及系统资源开销(CPU、内存)

    java 语言本身提供了对象序列化机制,也是 Java 语言本身最重要的底层机制之一,Java本身提供的序列化机制存在两个问题

    • 序列化的数据比较大,传输效率低
    • 其他语言无法识别和对接

    基于JDK序列化方式实现
    JDK 提供了 Java 对 象 的 序 列 化 方 式 , 主 要 通 过 输 出 流java.io.ObjectOutputStream 和对象输入流 java.io.ObjectInputStream来实现。其中,被序列化的对象需要实现java.io.Serializable接口

    提供序列化接口

    /**
     * @Project: 3.DistributedProject
     * @description:  序列化接口
     * @author: sunkang
     * @create: 2018-06-28 19:30
     * @ModificationHistory who      when       What
     **/
    public interface ISerializer {
    
        <T> byte[] serializer(T obj);
    
        <T> T  deSerailizer(byte[] bytes,Class<T> clazz);
    }
    

    email 实体类

    /**
     * @Project: 3.DistributedProject
     * @description:  email 实体类
     * @author: sunkang
     * @create: 2018-06-30 14:23
     * @ModificationHistory who      when       What
     **/
    public class Email implements Serializable {
    
        private String content;
    
        public String getContent() {
            return content;
        }
    
        public void setContent(String content) {
            this.content = content;
        }
    
        @Override
        public String toString() {
            return "Email{" +
                    "content='" + content + '\'' +
                    '}';
        }
    }
    

    person实体类

    /**
     * @Project: 3.DistributedProject
     * @description:   person实体类
     * @author: sunkang
     * @create: 2018-06-28 19:44
     * @ModificationHistory who      when       What
     **/
    public class Person implements Serializable,Cloneable {
    
        private  String name ;
        private  int age;
        private  String sex;
    
        private Email email;
    
        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 String getSex() {
            return sex;
        }
    
        public void setSex(String sex) {
            this.sex = sex;
        }
    
        public Email getEmail() {
            return email;
        }
    
        public void setEmail(Email email) {
            this.email = email;
        }
    
        @Override
        public String toString() {
            return "Person{" +
                    "name='" + name + '\'' +
                    ", age=" + age +
                    ", sex='" + sex + '\'' +
                    ", email=" + email +
                    '}';
        }
    }
    

    java序列化

    /**
     * @Project: 3.DistributedProject
     * @description:  java序列化的方式
     * @author: sunkang
     * @create: 2018-06-28 19:32
     * @ModificationHistory who      when       What
     **/
    public class JavaSerializer implements ISerializer {
        @Override
        public <T> byte[] serializer(T obj)  {
            ObjectOutputStream  oos = null;
            ByteArrayOutputStream byteArrayOutputStream= null;
            try {
                  byteArrayOutputStream =  new ByteArrayOutputStream();
                  oos = new ObjectOutputStream(byteArrayOutputStream);
                  oos.writeObject(obj);
                 return byteArrayOutputStream.toByteArray();
            } catch (IOException e) {
                e.printStackTrace();
            }finally {
                if(oos!=null){
                    try {
                        oos.close();
                    } catch (IOException e) {
                        e.printStackTrace();
                    }
                }
                if(byteArrayOutputStream != null){
                    try {
                        byteArrayOutputStream.close();
                    } catch (IOException e) {
                        e.printStackTrace();
                    }
                }
    
            }
    
            return null;
        }
    
        @Override
        public <T> T deSerailizer(byte[] bytes, Class<T> clazz) {
            ByteArrayInputStream byteArrayInputStream = null;
            ObjectInputStream  objectInputStream = null;
    
            try {
                 byteArrayInputStream  = new ByteArrayInputStream(bytes);
                objectInputStream =   new ObjectInputStream(byteArrayInputStream);
                return  (T)objectInputStream.readObject();
            } catch (IOException e) {
                e.printStackTrace();
            } catch (ClassNotFoundException e) {
                e.printStackTrace();
            }finally {
                if(byteArrayInputStream!=null){
                    try {
                        byteArrayInputStream.close();
                    } catch (IOException e) {
                        e.printStackTrace();
                    }
                }
                if(objectInputStream != null){
                    try {
                        objectInputStream.close();
                    } catch (IOException e) {
                        e.printStackTrace();
                    }
                }
            }
            return null;
        }
    }
    

    测试类:

    public class App 
    {
        public static void main( String[] args ){
            //java 序列化
            ISerializer serializer=  new JavaSerializer();
            Person person = new Person();
            person.setName("sunkang");
            person.setAge(23);
            person.setSex("nan");
            Email email = new Email();
            email.setContent("我的邮件");
            person.setEmail(email);
            byte[] javaBytes =serializer.serializer(person);
            Person  person1= serializer.deSerailizer(javaBytes,Person.class);
            System.out.println(person1.toString());
    }
    }
    

    输出结果

    Person{name='sunkang', age=23, sex='nan', email=Email{content='我的邮件'}}
    

    3.序列化的高阶认识

    • serialVersionUID的作用
      Java的序列化机制是通过判断类的serialVersionUID来验证版本一致性的。在进行反序列化时,JVM 会把传来的字节流中的 serialVersionUID与本地相应实体类的serialVersionUID进行比较,如果相同就认为是一致的,可以进行反序列化,否则就会出现序列化版本不一致的异常,即是InvalidCastException

      如果没有为指定的 class 配置 serialVersionUID,那么 java 编译器会自动给这个 class 进行一个摘要算法,类似于指纹算法, 只要这个文件有任何改动,得到的UID就会截然不同的,可以保证在这么多类中,这个编号是唯一的

    • serialVersionUID 有两种显示的生成方式
      一是默认的 1L ,比 如: private static final long serialVersionUID = 1L;
      二是根据类名、接口名、成员方法及属性等来生成一个64 位的哈希字段

    当实现 java.io.Serializable 接 口 的 类 没 有 显 式 地 定 义 一 个serialVersionUID 变量时候,Java 序列化机制会根据编译的 Class 自动生成一个 serialVersionUID 作序列化版本比较用,这种情况下,如果Class 文件(类名,方法明等)没有发生变化(增加空格,换行,增加注释等等),就算再编译多次,serialVersionUID 也不会变化的。

    • 静态变量序列化
      序列化保存的是对象的状态,静态变量属于类的状态,因此 序列化并不保存静态变量。

    • 父类的序列化
      1.子 类 继 承 该 父 类 并 且 实 现 了 序 列 化,此时父类并没有实现序列化,在反序列化该子类后,是没办法获取到父类的属性值的
      2.当一个父类实现序列化,子类自动实现序列化,不需要再显示实现Serializable 接口
      3.当一个对象的实例变量引用了其他对象,序列化该对象时 也会把引用对象进行序列化,但是前提是该引用对象必须实现序列化接口

    • Transient 关键字
      控制变量的序列化,在变量声明前加上该关键字,可以阻止该变量被序列化到文件中,在被反序列化后,transient变量的值被设为初始值, 如 int 型的是 0,对象型的是 null

    • 绕开 transient 机制的办法
      实体需要实现writeObject(ObjectOutputStream obj)
      readObject( ObjectInputStream objectInputStream ))的方法
      比如 hobby还是要序列化,需要手动实现这两个方法:

     private transient  String hobby;
        private void readObject(ObjectInputStream objectInputStream) throws IOException, ClassNotFoundException {
            objectInputStream.defaultReadObject();
            this.hobby = (String) objectInputStream.readObject();
        }
    
        private void writeObject(ObjectOutputStream objectOutputStream) throws IOException {
            objectOutputStream.defaultWriteObject();
            objectOutputStream.writeObject(this.hobby);
        }
    
    

    writeObject和 readObject 这两个私有的方法,既不属于 Object、也不是 Serializable,为什么能够在序列化的时候被调用呢?
    原因是:ObjectOutputStream使用了反射来寻找是否声明了这两个方法。因为 ObjectOutputStream使用 getPrivateMethod,所以这些方法必须声明为 priate 以至于供 ObjectOutputStream 来使用

    • 同一对象两次写入
    /**
     * @Project: serializer-demo
     * @description:  同一对象存储两次
     * @author: sunkang
     * @create: 2018-11-11 20:59
     * @ModificationHistory who      when       What
     **/
    public class StroreRuleDemo {
        public static void main(String[] args) throws IOException {
            ObjectOutputStream objectOutputStream = new ObjectOutputStream(new FileOutputStream("user.txt"));
            Person person = new Person();
            person.setName("sunkang");
            person.setAge(23);
            person.setSex("nan");
            Email email = new Email();
            email.setContent("我的邮件");
            person.setEmail(email);
            //第一次写入
            objectOutputStream.writeObject(person);
            objectOutputStream.flush();
            System.out.println(new File("user.txt").length());
            //第二次写入同一个对象
            objectOutputStream.writeObject(person);
            objectOutputStream.close();
            System.out.println(new File("user.txt").length());
        }
    }
    

    输出结果:可以发现第二次写入对象时文件只增加了 5 字节

    230
    235
    

    Java 序列化机制为了节省磁盘空间,具有特定的存储规则,当写入文件的为同一对象时,并不会再将对象的内容进行存储,而只是再次存储一份引用,上面增加的 5 字节的存储空间就是新增引用和一些控制信息的空间。反序列化时,恢复引用关系.该存储规则极大的节省了存储空间

    4.序列化实现深克隆

    在 Java 中存在一个 Cloneable 接口,通过实现这个接口的类都会具备clone 的能力,
    同时 clone 是在内存中进行,在性能方面会比我们直接通过 new 生成对象要高一些,特别是一些大的对象的生成,性能提升相对比较明显。那么在Java领域中,克隆分为深度克隆和浅克隆

    • 浅克隆: 被复制对象的所有变量都含有与原来的对象相同的值,而所有的对其他对象的引用仍然指向原来的对象
    • 深克隆: 被复制对象的所有变量都含有与原来的对象相同的值,除去那些引用其他对象的变量。那些引用其他对象的变量将指向被复制过的新对象,而不再是原有的那些被引用的对象。换言之,深拷贝把要复制的对象所引用的对象都复制了一遍

    浅克隆和深克隆实现可以参考: https://www.jianshu.com/p/34881452b171

    5.常见的序列化技术

    • 使用JAVA进行序列化
      优点:JAVA语言本身提供,使用比较方便和简单
      缺点:不支持跨语言处理、 性能相对不是很好,序列化以后产生的数据相对较大
      实现方式,前面的已经展示过了
    • XML 序列化框架
      XML序列化的好处在于可读性好,方便阅读和调试。但是序列化以后的字节码文件比较大,而且效率不高,适用于对性能不高,而且 QPS 较低的企业级内部系统之间的数据交换的场景,同时 XML 又具有语言无关性,所以还可以用于异构系统之间的数据交换和协议。比如我们熟知的Webservice,就是采用XML格式对数据进行序列化的

    先引用maven依赖,引用xstream的包完成xml到javabean的映射

        <dependency>
          <groupId>com.thoughtworks.xstream</groupId>
          <artifactId>xstream</artifactId>
          <version>1.4.10</version>
        </dependency>
    

    使用xstream 实现xml的序列化

    /**
     * @Project: 3.DistributedProject
     * @description:   xml实现序列化
     * @author: sunkang
     * @create: 2018-06-28 22:54
     * @ModificationHistory who      when       What
     **/
    public class xmlSerializer implements  ISerializer {
    
        XStream xStream = new XStream(new DomDriver());
        @Override
        public <T> byte[] serializer(T obj) {
            return xStream.toXML(obj).getBytes();
        }
    
        @Override
        public <T> T deSerailizer(byte[] bytes, Class<T> clazz) {
            return (T)xStream.fromXML(new String(bytes));
        }
    }
    
    • JSON 序列化框架
      JSON(JavaScript Object Notation)是一种轻量级的数据交换格式,相对于XML来说,JSON的字节流更小,而且可读性也非常好。现在JSON数据格式在企业运用是最普遍的
      JSON序列化常用的开源工具有很多
      1. Jacksonhttps://github.com/FasterXML/jackson
      2. 阿里开源的 FastJsonhttps://github.com/alibaba/fastjon
      3. Google 的 GSON (https://github.com/google/gson)
      这几种 json 序列化工具中,Jackson 与 fastjson 要比 GSON 的性能要好,但是 Jackson、GSON 的稳定性要比 Fastjson 好。而 fastjson 的优势在于提供的 api 非常容易使用

    先引用maven依赖

        <dependency>
          <groupId>com.alibaba</groupId>
          <artifactId>fastjson</artifactId>
          <version>1.2.29</version>
        </dependency>
    

    fastjson实现序列化

    /**
     * @Project: 3.DistributedProject
     * @description:  fastjson序列化
     * @author: sunkang
     * @create: 2018-06-28 22:44
     * @ModificationHistory who      when       What
     **/
    public class FastJsonSerializer implements ISerializer {
        @Override
        public <T> byte[] serializer(T obj) {
            return JSON.toJSONString(obj).getBytes();
        }
    
        @Override
        public <T> T deSerailizer(byte[] bytes, Class<T> clazz) {
            return JSON.parseObject(new String(bytes),clazz);
        }
    }
    
    • Hessian序列化框架
      Hessian是一个支持跨语言传输的二进制序列化协议,相对于Java默认的序列化机制来说, Hessian 具有更好的性能和易用性,而且支持多种不同的语言,实际上 Dubbo 采用的就是 Hessian 序列化来实现,只不过 Dubbo 对Hessian进行了重构,性能更高
    • Protobuf 序列化框架
      Protobuf是Google的一种数据交换格式,它独立于语言、独立于平台。
      Google提供了多种语言来实现,比如Java、C、Go、Python,每一种实现都包含了相应语言的编译器和库文件
      Protobuf 使用比较广泛,主要是空间开销小和性能比较好,非常适合用于公司内部对性能要求高的 RPC 调用。 另外由于解析性能比较高,序列化以后数据量相对较少,所以也可以应用在对象的持久化场景中但是但是要使用 Protobuf 会相对来说麻烦些,因为他有自己的语法,有自己的编译器 ,但是但是要使用 Protobuf 会相对来说麻烦些,因为他有自己的语法,有自己的编译器

    1)下载 protobuf 工具
    https://github.com/google/protobuf/releases 找到 protoc-3.5.1-win32.zip
    2)编写 proto 文件

    syntax="proto2";
    package com.gupaoedu.serial;
    option java_package = "com.gupaoedu.serial";
    option java_outer_classname="UserProtos";
    message User {
    required string name=1;
    required int32 age=2;
    }
    

    3)proto 的语法
    包名
    option 选项
    消息模型(消息对象、字段(字段修饰符-required/optional/repeated)
    字段类型(基本数据类型、枚举、消息对象)、字段名、标识号)
    4)生成实体类
    在 protoc.exe 安装目录下执行如下命令

    .\protoc.exe --java_out=./ ./user.proto
    

    5)运行查看结果
    将生成以后的 UserProto.java 拷贝到项目中


    image.png

    怎么使用protobuf可以先参考: https://www.ibm.com/developerworks/cn/linux/l-cn-gpb/index.html

    6.Protobuf 原理

    如果后面有机会用到protobuf,原理我会后面进行补充

    相关文章

      网友评论

          本文标题:(12)分布式系统的基石序列化和反序列化

          本文链接:https://www.haomeiwen.com/subject/cudffqtx.html