反序列化:将二进制字节流转换成对象的过程。
Java中对象都是存储在内存中,准确地说是JVM的堆或栈内存中,可以各个线程之间进行对象传输,但是无法在进程之间进行传输。如果涉及到跨内存的数据传输(比如两台机器的传输),直接把对象作为参数传递就不可取了,这时就需要通过“网络”将数据传输。
JDK
JDK的序列化方法使用简单,只需要实现Serializable接口即可,这个接口的作用只是表明这个类是可以被序列化的。
每个序列化类都有一个serialVersionID,如果不手动指定编译器也会动态生成。反序列化时JVM会根据serialVersionID找指定版本的class文件进行反序列化。
优缺点:
JDK序列化会把对象类的描述和所有属性的元数据都序列化为字节流,另外继承的元数据也会序列化,所以导致序列化的元素较多且字节流很大,但是由于序列化了所有信息所以相对而言更可靠。
优点:
- 使用简单,只需要在要序列化的类中实现Serializable接口即可。
- 可靠,jdk序列化会把对象类的描述和所有属性的元数据也序列化成字节流,所以信息可靠。
缺点:
- 不支持跨语言,因为是jdk自带的。
- 性能差,要序列化的信息太多导致序列化后的二进制流太大,传输成本大。
- 存在安全问题,序列化后的结果存在类的信息,如果攻击者构造一些恶意信息,会让反序列化产生一些非预期的对象。
public class JdkSerialization implements Serialization {
@Override
public <T> byte[] serialize(T object) {
try {
ByteArrayOutputStream baos = new ByteArrayOutputStream();
ObjectOutputStream oos = new ObjectOutputStream(baos);
oos.writeObject(object);
return baos.toByteArray();
} catch (IOException e) {
throw new SerializeException("Jdk serialize failed.", e);
}
}
@Override
public <T> T deserialize(Class<T> clazz, byte[] bytes) {
try {
ByteArrayInputStream bais = new ByteArrayInputStream(bytes);
ObjectInputStream ois = new ObjectInputStream(bais);
return (T) ois.readObject();
} catch (IOException | ClassNotFoundException e) {
throw new SerializeException("Jdk deserialize failed.", e);
}
}
}
JSON
JSON使用的是Gson实现,此外还可以使用FastJson、Jackson等实现JSON序列化。
优点:
- 可读性好:JSON使用文本格式,以键值对的形式表示对象和数据,易于阅读和调试。
- 广泛支持:几乎所有编程语言都有JSON的解析和生成库,方便各种应用之间的数据交换。
缺点:
- 性能较低:相对于二进制格式的序列化算法,JSON的序列化和反序列化速度较慢。
- 存储空间相对较大:相比其他算法,JSON产生的序列化数据通常比较冗余,占用较大的存储空间。
public class JsonSerialization implements Serialization {
/**
* 自定义 JavClass 对象序列化,解决 Gson 无法序列化 Class 信息
*/
static class ClassCodec implements JsonSerializer<Class<?>>, JsonDeserializer<Class<?>> {
@SneakyThrows
@Override
public Class<?> deserialize(JsonElement json, Type typeOfT, JsonDeserializationContext context) throws JsonParseException {
String name = json.getAsString();
return Class.forName(name);
}
@Override
public JsonElement serialize(Class<?> src, Type typeOfSrc, JsonSerializationContext context) {
// class -> json
return new JsonPrimitive(src.getName());
}
}
@Override
public <T> byte[] serialize(T object) {
try {
Gson gson = new GsonBuilder().registerTypeAdapter(Class.class, new ClassCodec()).create();
String json = gson.toJson(object);
return json.getBytes(StandardCharsets.UTF_8);
} catch (Exception e) {
throw new SerializeException("Json serialize failed.", e);
}
}
@Override
public <T> T deserialize(Class<T> clazz, byte[] bytes) {
try {
Gson gson = new GsonBuilder().registerTypeAdapter(Class.class, new ClassCodec()).create();
String json = new String(bytes, StandardCharsets.UTF_8);
return gson.fromJson(json, clazz);
} catch (JsonSyntaxException e) {
throw new SerializeException("Json deserialize failed.", e);
}
}
}
Hession
Hession和JDK自带的序列化方式类似,Hessian采用的也是二进制协议,只不过Hessian序列化之后,字节数更小,性能更优。
优点:
- 跨语言支持:Hessian可以在多种编程语言之间进行序列化和反序列化,方便实现跨平台的通信。
- 性能比jdk好,因为序列化的内容更少,例如如果该对象出现过就不会重新序列化,而是表示它的引用位置。并且序列化的格式更简洁。还有不会序列化属性的元数据等信息。
缺点:
- 性能相对较低:与Kryo和Protostuff相比,Hessian的性能稍差一些,尤其是在处理复杂对象和大数据量时。
public class HessianSerialization implements Serialization {
@Override
public <T> byte[] serialize(T object) {
try {
ByteArrayOutputStream baos = new ByteArrayOutputStream();
HessianSerializerOutput hso = new HessianSerializerOutput(baos);
hso.writeObject(object);
hso.flush();
return baos.toByteArray();
} catch (IOException e) {
throw new SerializeException("Hessian serialize failed.", e);
}
}
@Override
public <T> T deserialize(Class<T> clazz, byte[] bytes) {
try {
ByteArrayInputStream bis = new ByteArrayInputStream(bytes);
HessianSerializerInput hsi = new HessianSerializerInput(bis);
return (T) hsi.readObject();
} catch (IOException e) {
throw new SerializeException("Hessian deserialize failed.", e);
}
}
}
Kroy
号称Java 中最快的序列化框架。
Kryo 为了提供性能和减小序列化结果体积,提供注册的序列化对象类的方式。在注册时,会为该序列化类生成 int ID,后续在序列化时使用 int ID 唯一标识该类型。
Kryo 不是线程安全的。每个线程都应该有自己的 Kryo 对象、输入和输出实例。因此在多线程环境中,可以考虑使用 ThreadLocal 或者对象池来保证线程安全性。
优点:
- 高性能:Kryo在序列化和反序列化过程中具有很高的性能,尤其在处理复杂对象和大数据量时表现 出色。
- 体积小:Kryo生成的序列化数据通常比其他算法更紧凑,占用较小的存储空间。
- 兼容性:Kryo可以处理添加、删除或修改类定义的情况,并能与旧版本的数据进行兼容。
缺点:
- 不跨语言:Kryo是一种Java特定的序列化框架,不支持跨语言序列化。
- 可读性差:Kryo使用二进制格式,无法以人类可读的形式展示序列化数据。
public class KryoSerialization implements Serialization {
// kryo 线程不安全,所以使用 ThreadLocal 保存 kryo 对象
private final ThreadLocal<Kryo> kryoThreadLocal = ThreadLocal.withInitial(() -> {
Kryo kryo = new Kryo();
kryo.register(RpcRequest.class);
kryo.register(RpcResponse.class);
return kryo;
});
@Override
public <T> byte[] serialize(T object) {
try {
ByteArrayOutputStream baos = new ByteArrayOutputStream();
Output output = new Output(baos);
Kryo kryo = kryoThreadLocal.get();
// 将对象序列化为 byte 数组
kryo.writeObject(output, object);
kryoThreadLocal.remove();
return output.toBytes();
} catch (Exception e) {
throw new SerializeException("Kryo serialize failed.", e);
}
}
@Override
public <T> T deserialize(Class<T> clazz, byte[] bytes) {
try {
ByteArrayInputStream bais = new ByteArrayInputStream(bytes);
Input input = new Input(bais);
Kryo kryo = kryoThreadLocal.get();
// 将 byte 数组反序列化为 T 对象
T object = kryo.readObject(input, clazz);
kryoThreadLocal.remove();
return object;
} catch (Exception e) {
throw new SerializeException("Kryo deserialize failed.", e);
}
}
}
ProtoStuff
Protostuff使用schema来描述java对象的结构信息,包括字段名称、类型等。schema可以通过编译.proto文件生成,可以通过运行时动态生成。
在序列化时,根据schema将对象转换成二进制格式。
在反序列化时,根据schema将二进制转换成对象。读取二进制字节流的信息,根据schema完成解析和赋值。
优点:
- 高性能:protoStuff使用紧凑的二进制编码,序列化后的字节数量更少。
- 跨语言支持:Protostuff支持多种编程语言,并且在不同语言之间的序列化和反序列化效果相同。
缺点:
- 依赖schema:Protostuff需要通过schema来描述对象的结构信息,如果schema不正确则不能序列化成功。
- 如果java对象的字段发生变化时,会导致旧版本的字节流无法正常反序列化。
public class ProtostuffSerialization implements Serialization {
/**
* 提前分配好 Buffer,避免每次进行序列化都需要重新分配 buffer 内存空间
*/
private final LinkedBuffer BUFFER = LinkedBuffer.allocate(LinkedBuffer.DEFAULT_BUFFER_SIZE);
@SuppressWarnings({"unchecked", "rawtypes"})
@Override
public <T> byte[] serialize(T object) {
try {
Schema schema = RuntimeSchema.getSchema(object.getClass());
return ProtostuffIOUtil.toByteArray(object, schema, BUFFER);
} catch (Exception e) {
throw new SerializeException("Protostuff serialize failed.", e);
} finally {
// 重置 buffer
BUFFER.clear();
}
}
@Override
public <T> T deserialize(Class<T> clazz, byte[] bytes) {
try {
Schema<T> schema = RuntimeSchema.getSchema(clazz);
T object = schema.newMessage();
ProtostuffIOUtil.mergeFrom(bytes, object, schema);
return object;
} catch (Exception e) {
throw new SerializeException("Protostuff deserialize failed.", e);
}
}
}
总结
序列化算法 | 优点 | 缺点 |
JDK | 使用简单,可靠 | 性能极差,占空间,不跨语言 |
JSON | 格式简洁、可读性强 | 性能较差,占空间 |
Hession | 跨平台方便 | 性能较差 |
Kryo | 性能高,占空间小 | 不跨语言 |
ProtoStuff | 高性能 | 依赖schema |
性能对比:Kryo > ProtoStuff > JSON > Hession > JDK
空间开销:JKD > Hession > JSON > ProtoStuff > Kroy
以上数据来自https://code.google.com/p/thrift-protobuf-compare/wiki/Benchmarking