序列化与反序列化的那些事

前言:

    昨天开始构思要写一篇文章来说明一下序列化与反序列化。

    因为看到zookeeper的自定义序列化Jute,而且还自定义了通信协议。

    之前感觉这些都我来说都是很牛逼的存在,从来没想过要做这些东西,但是仔细一想,好像也么有很难实现的地方吧,当然这都是后话,我们先一点点分析,先写一下序列化与反序列化的事情,后面再手撸一个RPC的调用。

 

1.为什么需要序列化?

    如果面试的时候问到这个问题,那么每个人基本都有不同的见解,当然也会展示出每个人的技术深度。

    笔者的理解是:我们正常创建出来的对象都在内存中(一般都存在于堆空间中),而内存中的内容往往是不可靠的,如果服务器发生宕机,断电之后,那么内存中的这些对象信息都会消失。

    那如果我们还想继续用这些对象怎么办?

    那么只能将这些对象信息存储在不易消失的地方,比如硬盘(这个存储方式就随意发挥了,可以是RDBMS,也可以是文件)

 

    那么一个内存中的对象如何能保存在磁盘上的一个文件中呢?

    这个时候就用上序列化了!

 

2.如何进行序列化?

    实际这个问题就很简单了,分两个步骤:

    1)将内存中的对象转换为字节数组

    2)将字节数组存储到磁盘

 

3.序列化的方式?

    这个问题算是对第二个问题的补充,我们知道如何进行序列化之后,那么序列化的方式有哪些呢?

    回忆一下最常用的应该就是JDK自带的序列化了吧。那么我们通过一个示例来回忆一下。

 

    当然在这之前,我们首先要有一个实体类,如下所示,后面我们要做的序列化主要针对这个类的相关对象来做的。

public class Student implements Serializable {
    // 注意:这个实现序列化接口是必不可少的
    private static final long serialVersionUID = -1967707976943843439L;

    private String name;
    private int age;

    public Student(String name, int age) {
        this.name = name;
        this.age = age;
    }

    public Student() {
    }

    // getter setter方法省略...

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

    1)jdk序列化方式

public class JdkSerialize {
    public static void main(String[] args) {
        serialize();
    }

    private static void serialize() {
        Student jack = new Student("jack", 22);
        // 文件夹路径需要提前创建好
        String jdkFilePath = "D:\\serialize\\jdk\\student2.txt";
        try {
            // 序列化到文件中
            FileOutputStream fileOutputStream = new FileOutputStream(jdkFilePath);
            ObjectOutputStream dataOutputStream = new ObjectOutputStream(fileOutputStream);
            dataOutputStream.writeObject(jack);

            // 反序列化
            FileInputStream fileInputStream = new FileInputStream(jdkFilePath);
            ObjectInputStream objectInputStream = new ObjectInputStream(fileInputStream);
            Student jackCopy = (Student) objectInputStream.readObject();
            System.out.println(jackCopy);
        } catch (IOException e) {
            e.printStackTrace();
        } catch (ClassNotFoundException e) {
            e.printStackTrace();
        }
    }
}

// res
Student{name='jack', age=22}

    执行完上述序列化和反序列化方法后,我们可以看到在该文件路径生成的文件,有107字节。

    总结:这种方式是jdk自带的序列化方式,当然也是我们平时常用的一种方式。

    那么问题来了,这种jdk自带的方式是否就是最好的序列化方式呢?

    

    思维发散:

    那还有哪些方式可以将一个对象以字节的方式存储在文件中,然后还能从文件中重组出这个对象的全部信息呢?

 

    我们可以想一下,如果我们先把这个对象变成一个字符串,然后再将字符串转换成字节是不是就可以了,这个对我们来说实现方式是很简单的。

    有哪些方式可以将一个对象转换成字符串呢?最常用的就是Json,xml也可以,但是感觉xml比json长太多了,所以,我们就用json来实现下对象的序列化和反序列化来试试水

 

    2)JSON序列化方式

        笔者在这里选用FastJson来作为序列化工具,版本为1.2.58,具体如下:

public class JsoSerialize {

    public static void main(String[] args) {
        serialize();
    }

    private static void serialize() {
        Student jack = new Student("jack", 22);
        String path = "D:\\serialize\\json\\student.txt";

        try {
            // json序列化
            ObjectOutputStream dataOutputStream = new ObjectOutputStream(new FileOutputStream(path));
            String s = JSONObject.toJSONString(jack);
            dataOutputStream.writeObject(s);

            // json反序列化
            ObjectInputStream objectInputStream = new ObjectInputStream(new FileInputStream(path));
            String o = (String)objectInputStream.readObject();
            Student student = JSONObject.parseObject(o, Student.class);
            System.out.println(student);

        } catch (IOException e) {
            e.printStackTrace();
        } catch (ClassNotFoundException e) {
            e.printStackTrace();
        }
    }
}

// res
Student{name='jack', age=22}

    与刚才我们使用JDK反序列的结果一样。

    我们在其生成的文本中,可以看到只有24字节,比刚才jdk生成的字节更短了。

 

    实际在我们的开发过程中,Json还是一种不错的方式来进行对象的序列化,那么还有没有更好的方式呢?请看下面一种方式。

 

    3)Protobuf

        在这里就不科普这种方式,具体可以在网上搜一下,一大把相关资料。笔者只是把这种方式推出来,看下它生成的字节能不能更短小精悍。

        笔者使用proto3。

        * 大家需要先安装一个proto包;

        * 然后生成一下对应的.proto文件;

// student.proto
syntax = "proto3"; // PB协议版本

option java_package = "com.example.demo.serialize.protobuf";
option java_outer_classname="StudentProto";

message studentProtoTest {
    string name = 1;
    int32 age = 2;
}

        * 然后根据protoc工具生成.proto文件对应的java类。

// 进入student.proto文件所在的目录,执行protoc命令,如下:
protoc -I=./ --java_out=./ ./student.proto
// res:会生成一个StudentProto.java类

        * 根据生成的StudentProto.java类,进行序列化操作

public class ProtobufSerialize {
    public static void main(String[] args) {
        serialize();
    }

    private static void serialize() {

        StudentProto.studentProtoTest.Builder builder = StudentProto.studentProtoTest.newBuilder();
        builder.setName("lucy");
        builder.setAge(22);

        StudentProto.studentProtoTest build = builder.build();

        // 序列化
        byte[] bytes = build.toByteArray();
        System.out.println("byte length: " + bytes.length);

        // 反序列化
        StudentProto.studentProtoTest studentTestResult = null;
        try {
            studentTestResult = StudentProto.studentProtoTest.parseFrom(bytes);
            System.out.println(String.format("反序列化得到的信息,姓名:%s,性别:%d", studentTestResult.getName(), studentTestResult.getAge()));
        } catch (InvalidProtocolBufferException e) {
            e.printStackTrace();
        }
    }
}
// res:
byte length: 8
反序列化得到的信息,姓名:lucy,性别:22

        很令人的惊叹的结果,byte数组长度只有8,只有Json方式的三分之一,只有JDK方式的十二分之一。

        可能会有人问了,短了又怎样呢,有啥好处嘛?

        好处多多,我们同样的硬盘可以存储更多的对象;在网络传输时,我们同样的带宽可以传输更多的对象信息了,对企业来说都是巨大的成本,只有原成本的十分之一不到(JDK方式),就可以做同样的事情了。

 

总结:

    写在最后了,一个好的RPC框架,必定有一个优秀的对象序列化方式,所以,我们在选择对象的序列化方式时,可以多考察下。

    最后附上一个链接,比较常见的各种序列化方式(从时间和空间角度):https://github.com/eishay/jvm-serializers/wiki  

 

 

评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包

打赏作者

恐龙弟旺仔

你的鼓励将是我创作的最大动力

¥1 ¥2 ¥4 ¥6 ¥10 ¥20
扫码支付:¥1
获取中
扫码支付

您的余额不足,请更换扫码支付或充值

打赏作者

实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

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

余额充值