分布式系统之常见序列化方式案例

84 阅读3分钟

1、什么是序列化

引用百度百科的定义:序列化 (Serialization)是将对象的状态信息转换为可以存储或传输的形式的过程。与之对应的就是反序列化:将存储的或网络传输来的数据转为真实对象信息的过程。

2、为什么要序列化

了解计算机的都知道,在计算机和网络上只认识流的信息,即0/1,这就说明了我们直接定义的一个对象是没法在网络上直接传输的,这就需要一种手段将我们的对象转为流的状态,即序列化。

3、谁来进行序列化

对象由谁来使用、传输,则谁就需要进行序列化,从程序角度来看,对象就是由我们的程序来进行序列化,更直接一点就是写程序的人来序列化。

4、什么时候进行序列化

合适时机的序列化可以提高我们的资源利用,若一个对象下一步就需要传输或存储了,这个时候我们认为就需要序列化了,若过早的进行序列化,则在内存中白白的占用空间。

5、有哪些序列化方式

序列化方式是多样的,我们需要根据项目的实际情况来做出选择,在序列化过程中,从时间和空间上找到一种平衡。作为Java开发者,我们常见的序列化方式有:JDK自带的序列化方式、JSON、ProtoBuf、Hessian、Thrift、XML等。

6、如何进行序列化

这里用代码进行了一些测试,大家自己实践后做出选择。

package com.hz.cloud;

import java.io.Serializable;

/**
 * JDK自带的序列化
 * @author Dong
 * @version 1.0
 * @date 2022/3/30
 */
public class Person implements Serializable {

    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 "Person{" +
                "name='" + name + '\'' +
                ", age=" + age +
                '}';
    }
}
package com.hz.cloud;

/**
 * @author Dong
 * @version 1.0
 * @date 2022/3/30
 */
public class PersonInit {
    public static Person initPerson() {
        Person person = new Person();
        person.setName("公子奇");
        person.setAge(18);

        return person;
    }
}

6.1 JDK自带

package com.hz.cloud;

import java.io.*;

/**
 * @author Dong
 * @version 1.0
 * @date 2022/3/30
 */
public class JDKSerializable {
    public static void main(String[] args) throws IOException, ClassNotFoundException {
        Person person = PersonInit.initPerson();

        ByteArrayOutputStream baos = new ByteArrayOutputStream();

        //序列化
        ObjectOutputStream oos = new ObjectOutputStream(baos);
        long startTime = System.currentTimeMillis();
        for (int i = 0; i < 10000; i++) {
            oos.writeObject(person);
            oos.flush();
        }
        System.out.println("JDK自带序列化时间:" + (System.currentTimeMillis() - startTime) + " ms, " + "占用空间: " + baos.toByteArray().length);
        /*
         * 1 :JDK自带序列化时间:8 ms, 占用空间: 90
         * 10:JDK自带序列化时间:11 ms, 占用空间: 135
         * 100:JDK自带序列化时间:9 ms, 占用空间: 585
         * 1000:JDK自带序列化时间:11 ms, 占用空间: 5085
         * 10000:JDK自带序列化时间:10 ms, 占用空间: 50085
         */

        //反序列化
        ObjectInputStream ois = new ObjectInputStream(new ByteArrayInputStream(baos.toByteArray()));
        Person p = (Person) ois.readObject();
        System.out.println(p);
    }
}

JDK由于自身的局限性,我们一般不做选择。数据传输出去后,本身就是为了被别人使用,而JDK自带的序列化方式不支持跨语言,还有就是空间占用上远远高于数据本身。

6.2 JSON

JSON在当前分布式架构流行的情况下使用最广,越来越多的配置文件也开始用JSON了。

6.2.1 FastJson
package com.hz.cloud;

import com.alibaba.fastjson.JSON;
import com.alibaba.fastjson.JSONObject;

/**
 * @author Dong
 * @version 1.0
 * @date 2022/3/30
 */
public class FastJsonSerializable {
    public static void main(String[] args) {
        Person person = PersonInit.initPerson();

        // 序列化
        String json = null;
        long startTime = System.currentTimeMillis();
        for (int i = 0; i < 10000000; i++) {
            json = JSON.toJSONString(person);
        }
        System.out.println("FastJson序列化时间:" + (System.currentTimeMillis() - startTime) + " ms, " + "占用空间: " + json.getBytes().length);
        // 1: FastJson序列化时间:81 ms, 占用空间: 29
        // 10: FastJson序列化时间:80 ms, 占用空间: 29
        // 100: FastJson序列化时间:77 ms, 占用空间: 29
        // 1000: FastJson序列化时间:79 ms, 占用空间: 29
        // 10000: FastJson序列化时间:96 ms, 占用空间: 29
        // 100000: FastJson序列化时间:139 ms, 占用空间: 29
        // 1000000: FastJson序列化时间:229 ms, 占用空间: 29
        // 10000000: FastJson序列化时间:1240 ms, 占用空间: 29

        //反序列化
        Person p = JSON.parseObject(json, Person.class);
        System.out.println(p);
    }
}
6.2.2 Jackson
package com.hz.cloud;

import org.codehaus.jackson.map.ObjectMapper;
import org.codehaus.jackson.map.ObjectWriter;

import java.io.IOException;

/**
 * @author Dong
 * @version 1.0
 * @date 2022/3/30
 */
public class JacksonSerializable {
    public static void main(String[] args) throws IOException {
        Person person = PersonInit.initPerson();

        ObjectMapper mapper = new ObjectMapper();
        ObjectWriter writer = mapper.writerWithDefaultPrettyPrinter();

        // 序列化
        String json = null;
        long startTime = System.currentTimeMillis();
        for (int i = 0; i < 10000000; i++) {
            json = writer.writeValueAsString(person);
        }
        System.out.println("Jackson序列化时间:" + (System.currentTimeMillis() - startTime) + " ms, " + "占用空间: " + json.getBytes().length);
        // 1: Jackson序列化时间:22 ms, 占用空间: 43
        // 10: Jackson序列化时间:22 ms, 占用空间: 43
        // 100: Jackson序列化时间:27 ms, 占用空间: 43
        // 1000: Jackson序列化时间:33 ms, 占用空间: 43
        // 10000: Jackson序列化时间:50 ms, 占用空间: 43
        // 100000: Jackson序列化时间:110 ms, 占用空间: 43
        // 1000000: Jackson序列化时间:286 ms, 占用空间: 43
        // 10000000: Jackson序列化时间:1884 ms, 占用空间: 43

        //反序列化
        Person p = mapper.reader(Person.class).readValue(json);
        System.out.println(p);
    }
}
6.2.3 Gson
package com.hz.cloud;

import com.alibaba.fastjson.JSON;
import com.google.gson.Gson;

/**
 * @author Dong
 * @version 1.0
 * @date 2022/3/30
 */
public class GsonSerializable {
    public static void main(String[] args) {
        Person person = PersonInit.initPerson();

        Gson gson = new Gson();

        // 序列化
        String json = null;
        long startTime = System.currentTimeMillis();
        for (int i = 0; i < 1; i++) {
            json = gson.toJson(person);
        }
        System.out.println("Gson序列化时间:" + (System.currentTimeMillis() - startTime) + " ms, " + "占用空间: " + json.getBytes().length);
        // 1: Gson序列化时间:14 ms, 占用空间: 29
        // 10: Gson序列化时间:15 ms, 占用空间: 29
        // 100: Gson序列化时间:15 ms, 占用空间: 29
        // 1000: Gson序列化时间:23 ms, 占用空间: 29
        // 10000: Gson序列化时间:41 ms, 占用空间: 29
        // 100000: Gson序列化时间:138 ms, 占用空间: 29
        // 1000000: Gson序列化时间:532 ms, 占用空间: 29
        // 10000000: Gson序列化时间:4087 ms, 占用空间: 29

        //反序列化
        Person p = gson.fromJson(json, Person.class);
        System.out.println(p);
    }
}

6.3 Hessian

package com.hz.cloud;

import com.baidu.bjf.remoting.protobuf.Codec;
import com.baidu.bjf.remoting.protobuf.ProtobufProxy;
import com.caucho.hessian.io.Hessian2Input;
import com.caucho.hessian.io.Hessian2Output;

import java.io.ByteArrayInputStream;
import java.io.ByteArrayOutputStream;
import java.io.IOException;

/**
 * @author Dong
 * @version 1.0
 * @date 2022/3/30
 */
public class HessianSerializable {
    public static void main(String[] args) throws IOException {
        Person person = PersonInit.initPerson();

        ByteArrayOutputStream baos = new ByteArrayOutputStream();
        Hessian2Output out = new Hessian2Output(baos);

        // 序列化
        long startTime = System.currentTimeMillis();
        for (int i = 0; i < 1; i++) {
            out.writeObject(person);
        }
        out.close();
        System.out.println("Hessian序列化时间:" + (System.currentTimeMillis() - startTime) + " ms, " + "占用空间: " + baos.toByteArray().length);
        // 1: Hessian序列化时间:24 ms, 占用空间: 43
        // 10: Hessian序列化时间:27 ms, 占用空间: 61
        // 100: Hessian序列化时间:29 ms, 占用空间: 241
        // 1000: Hessian序列化时间:29 ms, 占用空间: 2041
        // 10000: Hessian序列化时间:31 ms, 占用空间: 16352
        // 100000: Hessian序列化时间:42 ms, 占用空间: 196224
        // 1000000: Hessian序列化时间:65 ms, 占用空间: 1994944
        // 10000000: Hessian序列化时间:368 ms, 占用空间: 19998496

        //反序列化
        ByteArrayInputStream bais = new ByteArrayInputStream(baos.toByteArray());
        Hessian2Input in = new Hessian2Input(bais);
        Person p = (Person) in.readObject();
        System.out.println(p);
    }
}

6.4 ProtoBuf

我们首先需要对上面的 Person类进行修改:

package com.hz.cloud;

import com.baidu.bjf.remoting.protobuf.FieldType;
import com.baidu.bjf.remoting.protobuf.annotation.Protobuf;

import java.io.Serializable;

/**
 * JDK自带的序列化
 * @author Dong
 * @version 1.0
 * @date 2022/3/30
 */
public class Person implements Serializable {

    @Protobuf(fieldType = FieldType.STRING)
    private String name;

    @Protobuf(fieldType = FieldType.INT32)
    private int age;

    ...
}
package com.hz.cloud;

import com.alibaba.fastjson.JSON;
import com.baidu.bjf.remoting.protobuf.Codec;
import com.baidu.bjf.remoting.protobuf.ProtobufProxy;
import com.baidu.bjf.remoting.protobuf.utils.ProtobufProxyUtils;
import com.baidu.jprotobuf.com.squareup.protoparser.ProtoParser;

import java.io.IOException;

/**
 * @author Dong
 * @version 1.0
 * @date 2022/3/30
 */
public class ProtobufSerializable {
    public static void main(String[] args) throws IOException {
        Person person = PersonInit.initPerson();

        Codec<Person> codec = ProtobufProxy.create(Person.class, false);

        // 序列化
        byte[] bytes = null;
        long startTime = System.currentTimeMillis();
        for (int i = 0; i < 1; i++) {
            bytes = codec.encode(person);
        }
        System.out.println("Protobuf序列化时间:" + (System.currentTimeMillis() - startTime) + " ms, " + "占用空间: " + bytes.length);
        // 1: Protobuf序列化时间:7 ms, 占用空间: 13
        // 10: Protobuf序列化时间:8 ms, 占用空间: 13
        // 100: Protobuf序列化时间:6 ms, 占用空间: 13
        // 1000: Protobuf序列化时间:10 ms, 占用空间: 13
        // 10000: Protobuf序列化时间:26 ms, 占用空间: 13
        // 100000: Protobuf序列化时间:90 ms, 占用空间: 13
        // 1000000: Protobuf序列化时间:778 ms, 占用空间: 13
        // 10000000: Protobuf序列化时间:5470 ms, 占用空间: 13

        //反序列化
        Person p = codec.decode(bytes);
        System.out.println(p);
    }
}

7、总结

从上面我们可以看到,Google 的 ProtoBuf 占用空间最小,小的占用在同样的网络环境下,传输必然也更快,ProtoBuf 使用的也越来越多。Json框架最多,使用的最广,不过各个框架的侧重点不一样,我们根据项目情况来做出选择。