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

2018-11-11  本文已影响0人  Mrsunup

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.序列化的高阶认识

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

 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.常见的序列化技术

先引用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));
    }
}

先引用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);
    }
}

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,原理我会后面进行补充

上一篇 下一篇

猜你喜欢

热点阅读