网络传输过程中,选择不同的传输数据的序列化方式,对性能影响很大,尤其是高并发场景中。但至于选择一个什么样的序列化方式很重要,也不能一味只考虑性能,像 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 | 二进制 | 良好 | 较紧凑 | 支持多种语言 | 分布式系统、远程调用 |
Gson | JSON(文本) | 良好 | 较大 | 广泛 | 需要人类可读性、快速开发 |
Fastjson2 | JSON(文本) | 高 | 较大 | 广泛 | 高性能 JSON 操作 |
2. 详细对比
性能:
- Kryo、Protostuff 和 FST 都是专注于 Java 的高性能序列化工具,在速度和效率上三者从高到低分别为:
Kryo > Protostuff > FST
。 - Hessian2 在跨语言远程调用中提供了良好的性能,但在速度上略逊于 Kryo、Protostuff 和 FST。
- FastJson2 和 Gson 在处理 JSON 数据时表现良好,但由于 JSON 是文本格式,其性能通常不如前面两类二进制格式,但 JSON 文本可读性高。二者性能上 FastJson2 略胜一筹。
- Kryo、Protostuff 和 FST 都是专注于 Java 的高性能序列化工具,在速度和效率上三者从高到低分别为:
总体性能上从高到低:Kryo > Protostuff > FST > Hessian2 > FastJson2 > Gson
数据大小:
- Kryo 和 Protostuff 都生成较为紧凑的二进制数据,二者都需要在服务提供端和消费端约定好使用一致的Java类(字段顺序都不能不同),这样在序列化之后的数据中只包含具体字段值,不包含类元数据,适合对数据大小敏感的应用。后面会详细介绍。
- 、FST 和 Hessian2 同样生成二进制数据,数据大小也较紧凑,但由于序列化后的数据中包含类元数据,因此不如前面二者紧凑。
- Gson 和 Fastjson2 因为使用 JSON 格式,数据较大,但提供了可读性。
跨语言支持:
- Hessian2 提供了良好的跨语言支持,适合分布式系统。
- Protostuff 基于 Protocol Buffers 的架构,具备一定的跨语言支持,但主要应用于 Java。
- Gson 和 Fastjson2 的 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
的一种扩展和补充。Protobuf
是 gRPC
的默认序列化方式,要求接口协议定义文件是 .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
是年龄。
- 数据格式:序列化后的数据是二进制格式,包含类 ID 和字段值。示例输出可能类似于
- 反序列化: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 可能更合适。
**粗体** _斜体_ [链接](http://example.com) `代码` - 列表 > 引用
。你还可以使用@
来通知其他用户。