网络传输过程中,选择不同的传输数据的序列化方式,对性能影响很大,尤其是高并发场景中。但至于选择一个什么样的序列化方式很重要,也不能一味只考虑性能,像 Dubbo协议中就支持多种可插拔的序列化库。

这里对 Protostuff、Kryo、Gson、fast Serialization (FST)、Fastjson2 和 Hessian2 这几种序列化方式进行对比介绍。

1. 对比归类

1. 对比分析
序列化工具格式性能数据大小跨语言支持适用场景
Protostuff二进制紧凑基于 Protocol Buffers,主要用于 Java高性能 Java 应用,需要紧凑序列化
Kryo二进制非常高紧凑主要用于 Java高性能 Java 应用、大数据处理
fast Serialization (FST)二进制紧凑主要用于 Java高性能 Java 应用
Hessian2二进制良好较紧凑支持多种语言分布式系统、远程调用
GsonJSON(文本)良好较大广泛需要人类可读性、快速开发
Fastjson2JSON(文本)较大广泛高性能 JSON 操作
2. 详细对比
  1. 性能

    • KryoProtostuffFST 都是专注于 Java 的高性能序列化工具,在速度和效率上三者从高到低分别为:Kryo > Protostuff > FST
    • Hessian2 在跨语言远程调用中提供了良好的性能,但在速度上略逊于 Kryo、Protostuff 和 FST。
    • FastJson2Gson 在处理 JSON 数据时表现良好,但由于 JSON 是文本格式,其性能通常不如前面两类二进制格式,但 JSON 文本可读性高。二者性能上 FastJson2 略胜一筹。

总体性能上从高到低:Kryo > Protostuff > FST > Hessian2 > FastJson2 > Gson

  1. 数据大小

    • KryoProtostuff 都生成较为紧凑的二进制数据,二者都需要在服务提供端和消费端约定好使用一致的Java类(字段顺序都不能不同),这样在序列化之后的数据中只包含具体字段值,不包含类元数据,适合对数据大小敏感的应用。后面会详细介绍。
    • FSTHessian2 同样生成二进制数据,数据大小也较紧凑,但由于序列化后的数据中包含类元数据,因此不如前面二者紧凑。
    • GsonFastjson2 因为使用 JSON 格式,数据较大,但提供了可读性。
  2. 跨语言支持

    • Hessian2 提供了良好的跨语言支持,适合分布式系统。
    • Protostuff 基于 Protocol Buffers 的架构,具备一定的跨语言支持,但主要应用于 Java。
    • GsonFastjson2 的 JSON 格式具有广泛的跨语言支持。

2. 代码简单使用

下面是对 Kryo、Protostuff、Fast Serialization (FST)、Hessian2、Fastjson2 和 Gson 的使用示例,包括它们的特点和序列化后的数据格式说明。

2.1. Kryo

特点

  • 高效的序列化速度和较小的序列化体积。
  • 需要类注册来提高性能。
  • 支持循环引用和对象图。

示例代码

import com.esotericsoftware.kryo.Kryo;
import com.esotericsoftware.kryo.io.Output;
import com.esotericsoftware.kryo.io.Input;
import java.io.ByteArrayOutputStream;
import java.io.ByteArrayInputStream;

public class KryoExample {
    public static void main(String[] args) {
        Kryo kryo = new Kryo();
        kryo.register(User.class); // 注册类

        // 序列化
        ByteArrayOutputStream baos = new ByteArrayOutputStream();
        Output output = new Output(baos);
        User user = new User("Alice", 30);
        kryo.writeObject(output, user);
        output.close();

        // 反序列化
        Input input = new Input(new ByteArrayInputStream(baos.toByteArray()));
        User deserializedUser = kryo.readObject(input, User.class);
        input.close();

        System.out.println(deserializedUser);
    }
}

class User {
    private String name;
    private int age;

    public User() {}

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

    // Getters and setters
}

数据格式

  • 二进制格式,紧凑,不包含字段名,依赖类注册信息。

2.2. Protostuff

Protostuff 是对 Protobuf 的一种扩展和补充。ProtobufgRPC 的默认序列化方式,要求接口协议定义文件是 .proto 格式,是可以跨语言的。而 Protostuff 可以理解为 Protobuf 的 Java 实现, 但底层原理是一样的。

特点

  • 基于 Protocol Buffers,序列化速度快。
  • 使用注解 @Tag 来标记字段。

示例代码

import io.protostuff.LinkedBuffer;
import io.protostuff.ProtostuffIOUtil;
import io.protostuff.Schema;
import io.protostuff.runtime.RuntimeSchema;

public class ProtostuffExample {
    public static void main(String[] args) {
        Schema<User> schema = RuntimeSchema.getSchema(User.class);
        LinkedBuffer buffer = LinkedBuffer.allocate(512);

        // 序列化
        User user = new User("Alice", 30);
        byte[] serializedData = ProtostuffIOUtil.toByteArray(user, schema, buffer);

        // 反序列化
        User deserializedUser = schema.newMessage();
        ProtostuffIOUtil.mergeFrom(serializedData, deserializedUser, schema);

        System.out.println(deserializedUser);
    }
}

class User {
    @io.protostuff.Tag(1)
    private String name;
    @io.protostuff.Tag(2)
    private int age;

    public User() {}

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

    // Getters and setters
}

数据格式

  • 二进制格式,紧凑,基于字段编号而不是名称。

2.3. Fast Serialization (FST)

特点

  • 速度快,支持多种 JVM 平台。
  • 支持对象图和循环引用。

示例代码

import org.nustaq.serialization.FSTConfiguration;

public class FSTExample {
    public static void main(String[] args) {
        FSTConfiguration fst = FSTConfiguration.createDefaultConfiguration();

        // 序列化
        User user = new User("Alice", 30);
        byte[] serializedData = fst.asByteArray(user);

        // 反序列化
        User deserializedUser = (User) fst.asObject(serializedData);

        System.out.println(deserializedUser);
    }
}

class User implements java.io.Serializable {
    private String name;
    private int age;

    public User() {}

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

    // Getters and setters
}

数据格式

  • 二进制格式,包含类的元数据,通过优化和压缩减少体积。

2.4. Hessian2

特点

  • 跨语言支持,适合 Java 和其他语言之间的通信。
  • 高效的二进制协议。

示例代码

import com.caucho.hessian.io.Hessian2Input;
import com.caucho.hessian.io.Hessian2Output;

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

public class Hessian2Example {
    public static void main(String[] args) throws Exception {
        // 序列化
        ByteArrayOutputStream baos = new ByteArrayOutputStream();
        Hessian2Output out = new Hessian2Output(baos);
        User user = new User("Alice", 30);
        out.writeObject(user);
        out.close();

        // 反序列化
        ByteArrayInputStream bais = new ByteArrayInputStream(baos.toByteArray());
        Hessian2Input in = new Hessian2Input(bais);
        User deserializedUser = (User) in.readObject();
        in.close();

        System.out.println(deserializedUser);
    }
}

class User implements java.io.Serializable {
    private String name;
    private int age;

    public User() {}

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

    // Getters and setters
}

数据格式

  • 二进制格式,适合跨语言使用,包含必要的元数据。

2.5. Fastjson2

特点

  • 高效的 JSON 序列化库,适用于 Java。
  • 支持多种数据格式和 Java 类型。

示例代码

import com.alibaba.fastjson2.JSON;

public class Fastjson2Example {
    public static void main(String[] args) {
        // 序列化
        User user = new User("Alice", 30);
        String jsonString = JSON.toJSONString(user);

        // 反序列化
        User deserializedUser = JSON.parseObject(jsonString, User.class);

        System.out.println(deserializedUser);
    }
}

class User {
    private String name;
    private int age;

    public User() {}

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

    // Getters and setters
}

数据格式

  • JSON 文本格式,易于阅读和调试。

2.6. Gson

特点

  • 谷歌提供的 JSON 序列化和反序列化库。
  • 易于使用,支持复杂的对象和泛型。

示例代码

import com.google.gson.Gson;

public class GsonExample {
    public static void main(String[] args) {
        Gson gson = new Gson();

        // 序列化
        User user = new User("Alice", 30);
        String jsonString = gson.toJson(user);

        // 反序列化
        User deserializedUser = gson.fromJson(jsonString, User.class);

        System.out.println(deserializedUser);
    }
}

class User {
    private String name;
    private int age;

    public User() {}

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

    // Getters and setters
}

数据格式

  • JSON 文本格式,易于阅读和调试。

3. Kryo、Protostuff 紧凑数据介绍

为了理解 Kryo 和 Protostuff 如何在传输过程中减少或消除元数据,我们需要深入探讨它们的工作机制、数据格式以及反序列化的过程。下面是详细的介绍和代码示例。

3.1. Kryo

1. 工作原理

Kryo 通过以下机制实现序列化时不包含显式元数据:

  • 类注册:在序列化之前,Kryo 允许你注册类。这种注册过程会为每个类分配一个整数 ID,这样在序列化时可以使用这个 ID 而不是类的全名。
  • 字段序列化:Kryo 根据类的结构序列化字段,而不是在每个对象中包含字段名或类型信息。
2. 序列化代码示例
import com.esotericsoftware.kryo.Kryo;
import com.esotericsoftware.kryo.io.Output;
import com.esotericsoftware.kryo.io.Input;
import java.io.ByteArrayOutputStream;
import java.io.ByteArrayInputStream;

public class KryoExample {
    public static void main(String[] args) {
        Kryo kryo = new Kryo();
        kryo.register(User.class); // 注册类

        // 序列化
        ByteArrayOutputStream baos = new ByteArrayOutputStream();
        Output output = new Output(baos);
        User user = new User("Alice", 30);
        kryo.writeObject(output, user);
        output.close();

        // 打印序列化后的字节数组
        byte[] serializedData = baos.toByteArray();
        System.out.println("Serialized data: " + java.util.Arrays.toString(serializedData));

        // 反序列化
        Input input = new Input(new ByteArrayInputStream(serializedData));
        User deserializedUser = kryo.readObject(input, User.class);
        input.close();

        System.out.println("Deserialized User: " + deserializedUser);
    }
}

class User {
    private String name;
    private int age;

    public User() {}

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

    @Override
    public String toString() {
        return "User{name='" + name + "', age=" + age + '}';
    }
}
3. 数据格式和反序列化
    • 数据格式:序列化后的数据是二进制格式,包含类 ID 和字段值。示例输出可能类似于 [1, 5, 65, 108, 105, 99, 101, 30],其中 1 是类 ID,5 是字符串长度,65, 108, 105, 99, 101 是 "Alice" 的 ASCII 编码,30 是年龄。
  • 反序列化:Kryo 使用注册时的类信息(类 ID 和字段顺序)来正确地解析和重建对象。

所以服务提供者、服务消费者在序列化、反序列化使用的POJO类结构最好要一致,如上述,就算调整了字段顺序也会导致反序列化失败。

3.2. Protostuff

1. 工作原理

Protostuff 基于 Protocol Buffers 的设计,使用以下机制:

  • 字段编号:每个字段通过 @Tag 注解分配一个唯一编号,序列化时使用编号而不是字段名。
  • Schema:定义数据结构的 Schema 确保在序列化和反序列化时结构一致。

序列化代码示例

import io.protostuff.LinkedBuffer;
import io.protostuff.ProtostuffIOUtil;
import io.protostuff.Schema;
import io.protostuff.runtime.RuntimeSchema;

public class ProtostuffExample {
    public static void main(String[] args) {
        Schema<User> schema = RuntimeSchema.getSchema(User.class);
        LinkedBuffer buffer = LinkedBuffer.allocate(512);

        // 序列化
        User user = new User("Alice", 30);
        byte[] serializedData = ProtostuffIOUtil.toByteArray(user, schema, buffer);

        // 打印序列化后的字节数组
        System.out.println("Serialized data: " + java.util.Arrays.toString(serializedData));

        // 反序列化
        User deserializedUser = schema.newMessage();
        ProtostuffIOUtil.mergeFrom(serializedData, deserializedUser, schema);

        System.out.println("Deserialized User: " + deserializedUser);
    }
}

class User {
    @io.protostuff.Tag(1)
    private String name;
    @io.protostuff.Tag(2)
    private int age;

    public User() {}

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

    @Override
    public String toString() {
        return "User{name='" + name + "', age=" + age + '}';
    }
}
2. 数据格式和反序列化
  • 数据格式:序列化后的数据是二进制格式,包含字段编号和字段值。示例输出可能类似于 [10, 5, 65, 108, 105, 99, 101, 16, 30],其中 10 表示字段编号 1 的长度为 5,65, 108, 105, 99, 101 是 "Alice" 的 UTF-8 编码,16, 30 表示字段编号 2 的值为 30。
  • 反序列化:Protostuff 使用 Schema 来解析数据,Schema 定义了字段编号和类型信息,从而能够正确地重建对象。

@Tag 注解增加了一定的容错性,因为字段顺序是根据 @Tag 注解中的值来的,如果实在要调整类中字段顺序,不改变注解值没关系。

3.3. 对比

在比较 Kryo 和 Protostuff 的序列化数据大小时,需要考虑它们各自的工作机制和优化策略。

1. Kryo
  • 类注册:Kryo 通过类注册减少元数据的大小。类的完整名称不会被序列化,而是使用一个整数 ID。
  • 字段顺序:Kryo 序列化时不存储字段名,而是依赖字段的序列化顺序。这意味着它不需要额外的字段标识符。
  • 紧凑编码:Kryo 使用紧凑的编码方式来表示基本数据类型。
2. Protostuff
  • 字段编号:Protostuff 基于 Protocol Buffers,使用字段编号而不是字段名进行序列化。这会稍微增加数据大小,因为每个字段都需要一个编号。
  • Schema:在反序列化时需要 Schema 来解析数据。
3. 比较
  • 数据大小:Kryo 通常能生成比 Protostuff 更小的序列化数据,因为它完全依赖于字段的顺序,而不是为每个字段存储编号。
  • 灵活性:Protostuff 提供了更好的向后兼容性,因为它使用字段编号。这意味着如果字段顺序改变,数据仍然可以正确解析。

在绝大多数情况下,Kryo 的序列化数据会比 Protostuff 更小,特别是对于简单的对象结构。然而,Protostuff 提供了更多的灵活性和跨语言支持,尤其是在字段变更和兼容性方面。

因此,选择哪个库取决于你的具体需求。如果数据大小是最重要的考虑因素,并且你只在 Java 生态系统中工作,Kryo 可能是更好的选择。如果需要跨语言支持和更好的向后兼容性,Protostuff 可能更合适。


KerryWu
641 声望159 粉丝

保持饥饿


引用和评论

0 条评论