序列化和反序列化
前言
Github:https://github.com/HealerJean
一、什么是序列化/反序列化?
1、概念
⬤ 序列化是指将内存中的一个对象转化成字节序列(对象的数据、类型及对象中存储的属性),通俗点讲序列化就是冻结了那一刻内存中对象的状态,以便进行保存、程序间通信或者进行网络通信;
⬤ 反序列化就正好相反,将字节流转化成内存中的对象信息,将序列化冻结的对象状态进行解冻还原。
2、意义
序列化解决了如何在不同的环境(如不同的内存空间、不同的机器、不同的应用程序之间)以及不同的时间点(即现在和将来)有效、安全地传输和存储对象状态的问题。
它是现代软件开发中数据处理和交换的关键技术,特别是在分布式系统、网络通信、数据持久化和复杂对象管理中扮演着重要角色。
分布式系统:在 RPC(远程过程调用)中,客户端和服务端需要传递对象,序列化与反序列化在其中扮演了重要角色。
网络传输:通过网络传输对象时,序列化可以将对象转为字节流,便于传输。
对象数据持久化:在 Java 中,可以通过序列化将对象保存到磁盘中,比如将一个 Java 对象保存到文件中,然后在需要时恢复该对象。
缓存机制:将对象序列化后存储在缓存中,并在需要时通过反序列化来使用。
3、序列化的格式
序列化可以采取多种格式,包括但不限于:
二进制格式(如:
Hessian、Kryo、Protobuf、ProtoStuff等):这种格式高效但不易读。文本格式(如
JSON、XML):序列化结果易读,便于调试,但通常比二进制格式占用空间大。自定义格式:特定应用可能开发专用的序列化格式来优化性能或安全性。
4、实现 Serializable 接口
部分序列化实现要求实现该接口:
Serializable接口本身不含有人和参数或者方法定义,所以他自身是无任何功能,可以视作为一种标记或规范,类似于@Override。真能整发挥作用的是具体实现存储、传输和解读的方法
1)序列化 ID:
如果没有特殊需求,就是用默认的
1L就可以,这样可以确保代码一致时反序列化成功。这也可能是造成序列化和反序列化失败的原因,因为不同的序列化id之间不能进行序列化和反序列化。如果使用的是long类型的数据,表明实现序列化类的不同版本间的兼容性。如果你修改了此类, 要修改此值
2)所有的类都需要手动的实现吗?
Java类中有其实已经有好多类已经默默的实现了Serializable接口,方便我们转换数据,其中直接实现的有String,File,Character,Number,ArrayList,LinkList,HashMap,HashTable,HashSet………
二、序列化方式
public interface Serializer {
/**
* 对对象进行编码
*
* @param obj 要编码的对象
* @return 编码后的字节数组
*/
byte[] serialize(Object obj);
/**
* 将字节数组解码为指定类型的对象
*
* @param bytes 要解码的字节数组
* @param clazz 要解码成的对象的类型
* @return 解码后得到的对象
*/
<T> T deserialize(byte[] bytes, Class<T> clazz);
/**
* 获取序列化协议类型
*
* @return 序列化协议类型
*/
SerializationType protocolType();
/**
* 序列化器实例销毁时做些什么
*/
default void unregister() {
}
}
@Getter
@AllArgsConstructor
public enum SerializationType {
JDK((byte) 1),
JSON((byte) 2),
MSGPACK((byte) 3),
HESSIAN((byte) 4),
PROTOSTUFF((byte) 5),
KRYO((byte) 6),
;
private final byte code;
public static SerializationType fromCode(byte code) {
for (SerializationType protocolType : values()) {
if (protocolType.code == code) {
return protocolType;
}
}
return null;
}
}
public class SerializeException extends RuntimeException {
/**
* SerializeException构造函数,使用给定的消息初始化异常
*
* @param message 异常消息
*/
public SerializeException(String message) {
super(message);
}
/**
* 构造一个带指定详细消息和原因的新异常。
*
* @param message 详细消息描述
* @param cause 异常原因
*/
public SerializeException(String message, Throwable cause) {
super(message, cause);
}
}
1、JDK(线程安全)Serializable
JDK自带的序列化,目标类必须实现java.io.Serializable接口,序列化和反序列化操作无外部依赖。
public class JdkSerializer implements Serializer {
/**
* INSTANCE
*/
private static volatile Serializer INSTANCE;
/**
* 私有构造函数
*/
private JdkSerializer() {
}
/**
* 获取序列化器实例
* @return 返回序列化器实例
*/
public static Serializer getInstance() {
if (INSTANCE == null) {
synchronized (JdkSerializer.class) {
if (INSTANCE == null) {
INSTANCE = new JdkSerializer();
}
}
}
return INSTANCE;
}
@Override
public <T> T deserialize(byte[] bytes, Class<T> clazz) {
try (ObjectInputStream ois = new ObjectInputStream(new ByteArrayInputStream(bytes))) {
return (T) ois.readObject();
} catch (ClassNotFoundException | IOException e) {
throw new SerializeException("jdk反序列化出现异常", e);
}
}
@Override
public SerializationType protocolType() {
return SerializationType.JDK;
}
@Override
public byte[] serialize(Object obj) {
try (ByteArrayOutputStream bos = new ByteArrayOutputStream();
ObjectOutputStream oos = new ObjectOutputStream(bos)) {
oos.writeObject(obj);
return bos.toByteArray();
} catch (IOException e) {
throw new SerializeException("jdk序列化出现异常", e);
}
}
2、Hessian(跨语言-线程安全)Serializable
Hessian是一个比较老的序列化实现了,并且同样也是跨语言的。Dubbo2.x默认启用的序列化方式是Hessian2,但是,Dubbo对Hessian2进行了修改,不过大体结构还是差不多。
<!-- hessian -->
<dependency>
<groupId>com.caucho</groupId>
<artifactId>hessian</artifactId>
<version>4.0.60</version>
</dependency>
public class HessianSerializer implements Serializer {
/**
* INSTANCE
*/
private static volatile Serializer INSTANCE;
/**
* 构造方法
*/
private HessianSerializer() {
}
/**
* 获取序列化器实例
*
* @return 返回序列化器实例
*/
public static Serializer getInstance() {
if (INSTANCE == null) {
synchronized (HessianSerializer.class) {
if (INSTANCE == null) {
INSTANCE = new HessianSerializer();
}
}
}
return INSTANCE;
}
@Override
public <T> T deserialize(byte[] bytes, Class<T> clazz) {
if (bytes == null) {
return null;
}
try (ByteArrayInputStream is = new ByteArrayInputStream(bytes)) {
Hessian2Input input = new JDSafeHessian2Input(is);
return (T) input.readObject();
} catch (Exception e) {
throw new SerializeException("hessian反序列化出现异常", e);
}
}
@Override
public byte[] serialize(Object obj) {
try (ByteArrayOutputStream os = new ByteArrayOutputStream()) {
Hessian2Output output = new Hessian2Output(os);
output.writeObject(obj);
output.flush();
return os.toByteArray();
} catch (Exception e) {
throw new SerializeException("hessian序列化出现异常", e);
}
}
@Override
public SerializationType protocolType() {
return SerializationType.HESSIAN;
}
}
3、JSON (跨语言-线程安全)
JSON序列化可以使用多种方式实现,如fastjson、jackson、gson,这里以fastjson为例。
<dependency>
<groupId>com.alibaba</groupId>
<artifactId>fastjson</artifactId>
<version>1.2.83-jdsec.rc1</version>
</dependency>
public class JsonSerializer implements Serializer {
/**
* INSTANCE
*/
private static volatile Serializer INSTANCE;
/**
* 构造函数
*/
private JsonSerializer() {
}
/**
* 获取单例实例
*
* @return 返回单例实例
*/
public static Serializer getInstance() {
if (INSTANCE == null) {
synchronized (JsonSerializer.class) {
if (INSTANCE == null) {
INSTANCE = new JsonSerializer();
}
}
}
return INSTANCE;
}
@Override
public <T> T deserialize(byte[] bytes, Class<T> clazz) {
String json = new String(bytes, StandardCharsets.UTF_8);
return JSON.parseObject(json, clazz);
}
@Override
public byte[] serialize(Object obj) {
if (obj == null) {
return new byte[0];
}
return JSON.toJSONString(obj).getBytes(StandardCharsets.UTF_8);
}
@Override
public SerializationType protocolType() {
return SerializationType.JSON;
}
}
4、Kryo(java 非线程安全)
Kryo是一个快速序列化/反序列化工具,依赖于字节码生成机制(底层使用了ASM库),因此在序列化速度上有一定的优势,但正因如此,其使用也只能限制在基于JVM的语言上,Kryo已经是一种非常成熟的序列化实现了,已经在Groupon、Yahoo以及多个著名开源项目(如Hive、Storm)中广泛的使用
Kryo是专门针对Java语言序列化方式并且性能非常好,如果你的应用是专门针对Java语言的话可以考虑使用,并且Dubbo官网的一篇文章中提到说推荐使用Kryo作为生产环境的序列化方式⚠️ 但是
Kryo不是线程安全的,每个线程都应该有自己的Kryo对象、输入和输出实例。在多线程环境中,可以使用Kryo对象池解决这个问题。关于
Kryo的更多知识可参考 kryo github 和 Kryo序列化
<dependency>
<groupId>com.esotericsoftware</groupId>
<artifactId>kryo</artifactId>
<version>4.0.1</version>
<optional>true</optional>
</dependency>
public class KryoSerializer implements Serializer {
/**
* INSTANCE
*/
private static volatile KryoSerializer INSTANCE;
private final KryoPool pool;
/**
* 构造函数
*/
private KryoSerializer() {
this.pool = new KryoPool.Builder(() -> {
Kryo kryo = new Kryo();
// Kryo 配置
kryo.setReferences(false);
kryo.setRegistrationRequired(false);
return kryo;
}).build();
}
/**
* 获取单例实例
*
* @return 返回实例
*/
public static KryoSerializer getInstance() {
if (INSTANCE == null) {
synchronized (KryoSerializer.class) {
if (INSTANCE == null) {
INSTANCE = new KryoSerializer();
}
}
}
return INSTANCE;
}
@Override
public byte[] serialize(Object obj) {
Kryo kryo = pool.borrow();
try (ByteArrayOutputStream bos = new ByteArrayOutputStream()) {
Output output = new Output(bos);
kryo.writeObject(output, obj);
output.close();
return bos.toByteArray();
} catch (IOException e) {
throw new SerializeException("kryo序列化出现异常", e);
} finally {
pool.release(kryo);
}
}
@Override
public <T> T deserialize(byte[] bytes, Class<T> clazz) {
Kryo kryo = pool.borrow();
try (Input input = new Input(new ByteArrayInputStream(bytes))) {
return kryo.readObject(input, clazz);
} finally {
pool.release(kryo);
}
}
@Override
public SerializationType protocolType() {
return SerializationType.KRYO;
}
}
5、ProtoStuff(跨语言-线程安全)
ProtoStuff是一个基于Protobuf实现的序列化方法,它较于Protobuf最明显的好处是,在几乎不损耗性能的情况下做到了不用我们写 ``proto文件来实现序列化,关于Protobuf ` 不做介绍。⬤
Protobuf出自于IDL文件和生成对应的序列化代码。这样虽然不灵活,但是,另一方面导致protobuf没有序列化漏洞的风险。由于Protobuf的易用性,它的弟弟Protostuff诞生了。⬤
protostuff基于protobuf,但是提供了更多的功能和更简易的用法。虽然更加易用,但是不代表ProtoStuff性能更差。
<!-- For the core formats (protostuff, protobuf, graph) -->
<dependency>
<groupId>io.protostuff</groupId>
<artifactId>protostuff-core</artifactId>
<version>1.7.4</version>
<optional>true</optional>
</dependency>
<!-- For schemas generated at runtime -->
<dependency>
<groupId>io.protostuff</groupId>
<artifactId>protostuff-runtime</artifactId>
<version>1.7.4</version>
<optional>true</optional>
</dependency>
public class ProtostuffSerializer implements Serializer {
/**
* INSTANCE
*/
private static volatile Serializer INSTANCE;
/**
* 缓存Schema
*/
private static Map<Class<?>, Schema<?>> schemaCache;
/**
* 构造函数
*/
private ProtostuffSerializer() {
}
/**
* 获取单例实例
*
* @return 返回单例实例
*/
public static Serializer getInstance() {
if (INSTANCE == null) {
synchronized (ProtostuffSerializer.class) {
if (INSTANCE == null) {
schemaCache = new ConcurrentHashMap<>();
INSTANCE = new ProtostuffSerializer();
}
}
}
return INSTANCE;
}
@Override
public byte[] serialize(Object obj) {
// serialize
LinkedBuffer buffer = LinkedBuffer.allocate(LinkedBuffer.DEFAULT_BUFFER_SIZE);
try {
return ProtostuffIOUtil.toByteArray(obj, this.registerSchema(obj.getClass()), buffer);
} finally {
buffer.clear();
}
}
@Override
public <T> T deserialize(byte[] bytes, Class<T> clazz) {
Schema<T> schema = this.registerSchema(clazz);
T obj = schema.newMessage();
ProtostuffIOUtil.mergeFrom(bytes, obj, schema);
return obj;
}
@Override
public SerializationType protocolType() {
return SerializationType.PROTOSTUFF;
}
public Schema registerSchema(Class clazz) {
Schema schema = schemaCache.get(clazz);
if (schema == null) {
// this is lazily created and cached by RuntimeSchema
// so its safe to call RuntimeSchema.getSchema(Foo.class) over and over
// The getSchema method is also thread-safe
schema = RuntimeSchema.getSchema(clazz);
schemaCache.put(clazz, schema);
}
return schema;
}
@Override
public void unregister() {
schemaCache.clear();
schemaCache = null;
}
}
6、MsgPack(跨语言-线程安全)
MessagePack· 是一种高效的二进制序列化格式。它允许您在多种语言之间交换数据,例如JSON。但它更快、更小。小整数被编码为单个字节,典型的短字符串除了字符串本身之外只需要一个额外的字节。
<dependency>
<groupId>org.msgpack</groupId>
<artifactId>jackson-dataformat-msgpack</artifactId>
<version>0.9.8</version>
</dependency>
public class MsgPackSerializer implements Serializer {
private static volatile MsgPackSerializer INSTANCE;
private static ObjectMapper objectMapper;
private MsgPackSerializer() {
}
/**
* 获取序列化器实例
*
* @return 返回序列化器实例
*/
public static Serializer getInstance() {
if (INSTANCE == null) {
synchronized (MsgPackSerializer.class) {
if (INSTANCE == null) {
objectMapper = new MessagePackMapper();
INSTANCE = new MsgPackSerializer();
}
}
}
return INSTANCE;
}
@Override
public byte[] serialize(Object obj) {
try (ByteArrayOutputStream baos = new ByteArrayOutputStream()) {
objectMapper.writeValue(baos, obj);
return baos.toByteArray();
} catch (IOException e) {
throw new SerializeException("msgpack序列化异常", e);
}
}
@Override
public <T> T deserialize(byte[] bytes, Class<T> clazz) {
try {
return objectMapper.readValue(bytes, clazz);
} catch (IOException e) {
throw new SerializeException("反序列化异常", e);
}
}
@Override
public SerializationType protocolType() {
return SerializationType.MSGPACK;
}
}
7、工具 SerializationUtils
SerializationUtils.clone(Object)方法在内部使用了Java的序列化机制来创建对象的深拷贝。因此,它要求被拷贝的对象及其所有非瞬态(non-transient)和非静态(non-static)字段都必须实现了Serializable接口。如果对象图中有任何字段没有实现Serializable接口,或者包含了不支持序列化的类型(如java.io.File),那么序列化过程将会失败,并抛出NotSerializableException或其他异常。
public class ExampleClass implements Serializable {
private static final long serialVersionUID = 1L;
// 静态字段,不会被序列化
private static String staticField = "Static Value";
// 瞬态字段,不会被序列化
private transient String transientField = "Transient Value";
// 非瞬态且非静态字段,会被序列化
private String nonTransientNonStaticField = "Non-Transient, Non-Static Value";
// 不会被序列化
private int baseInt;
// ... 其他代码,如getter和setter ...
}
三、对比测试
1、单测执行
@Getter
@Setter
public final class SerializableBean implements Serializable {
private static final long serialVersionUID = 12345L;
private static String staticField;
private String strVal;
private Integer intVal;
private Long longVal;
private BigDecimal bigDecimal;
private Date date;
private Class<?> genericClazz;
private Class<?> interfaceClazz;
public static void setStaticField(String staticField) {
SerializableBean.staticField = staticField;
}
public static String getStaticField() {
return staticField;
}
@Override
public String toString() {
return "SerializableBean{" +
"staticField='" + staticField + '\'' +
", strVal='" + strVal + '\'' +
", intVal=" + intVal +
", longVal=" + longVal +
", bigDecimal=" + bigDecimal +
", date=" + date +
", genericClazz=" + genericClazz +
", interfaceClazz=" + interfaceClazz +
'}';
}
}
@Slf4j
public class SerializerTest {
private static SerializableBean serializableBean;
@BeforeClass
public static void beforeClass() {
serializableBean = new SerializableBean();
serializableBean.setStrVal("serializableBean");
serializableBean.setIntVal(1);
serializableBean.setLongVal(2L);
serializableBean.setBigDecimal(new BigDecimal("999"));
serializableBean.setDate(new Date());
serializableBean.setInterfaceClazz(Serializer.class);
serializableBean.setGenericClazz(SerializerFactory.class);
SerializableBean.setStaticField("staticField");
}
@Test
public void testSerializer() {
testSerializer(JdkSerializer.getInstance());
testSerializer(HessianSerializer.getInstance());
testSerializer(JsonSerializer.getInstance());
testSerializer(KryoSerializer.getInstance());
testSerializer(ProtostuffSerializer.getInstance());
testSerializer(MsgPackSerializer.getInstance());
}
/**
* 测试序列化器的方法
*
* @param serializer 序列化器对象
* @return void
*/
private void testSerializer(Serializer serializer) {
final String name = serializer.getClass().getName();
log.info(String.format("---------------- %s ----------------", name));
byte[] bytes = serializer.serialize(serializableBean);
// log.info("code result size: {}, bytes: {}", bytes.length, Arrays.toString(bytes));
log.info("serialize result size: {}", bytes.length);
SerializableBean decode = serializer.deserialize(bytes, SerializableBean.class);
log.info("deserialize result: {}", decode);
serializer.unregister();
// log.info(String.format("---------------- %s ----------------\n", name));
}
}
################
2024-10-22 18:13:47 INFO -[ ]- ---------------- com.healerjean.proj.a_test.serializeable.utils.JdkSerializer ---------------- com.healerjean.proj.a_test.serializeable.test.SerializerTest.testSerializer[58]
serialize result size: 873
deserialize result: SerializableBean{staticField='staticField', strVal='serializableBean', intVal=1, longVal=2, bigDecimal=999, date=Tue Oct 22 18:13:47 CST 2024, genericClazz=class com.caucho.hessian.io.SerializerFactory, interfaceClazz=interface com.healerjean.proj.a_test.serializeable.base.Serializer}
################
################
2024-10-22 18:13:47 INFO -[ ]- ---------------- com.healerjean.proj.a_test.serializeable.utils.HessianSerializer ---------------- com.healerjean.proj.a_test.serializeable.test.SerializerTest.testSerializer[58]
serialize result size: 319
deserialize result: SerializableBean{staticField='staticField', strVal='serializableBean', intVal=1, longVal=2, bigDecimal=999, date=Tue Oct 22 18:13:47 CST 2024, genericClazz=class com.caucho.hessian.io.SerializerFactory, interfaceClazz=interface com.healerjean.proj.a_test.serializeable.base.Serializer}
################
################
2024-10-22 18:13:47 INFO -[ ]- ---------------- com.healerjean.proj.a_test.serializeable.utils.JsonSerializer ---------------- com.healerjean.proj.a_test.serializeable.test.SerializerTest.testSerializer[58]
serialize result size: 223
deserialize result: SerializableBean{staticField='staticField', strVal='serializableBean', intVal=1, longVal=2, bigDecimal=999, date=Tue Oct 22 18:13:47 CST 2024, genericClazz=class com.caucho.hessian.io.SerializerFactory, interfaceClazz=interface com.healerjean.proj.a_test.serializeable.base.Serializer}
################
################
2024-10-22 18:13:47 INFO -[ ]- ---------------- com.healerjean.proj.a_test.serializeable.utils.KryoSerializer ---------------- com.healerjean.proj.a_test.serializeable.test.SerializerTest.testSerializer[58]
serialize result size: 169
deserialize result: SerializableBean{staticField='staticField', strVal='serializableBean', intVal=1, longVal=2, bigDecimal=999, date=Tue Oct 22 18:13:47 CST 2024, genericClazz=class com.caucho.hessian.io.SerializerFactory, interfaceClazz=interface com.healerjean.proj.a_test.serializeable.base.Serializer}
################
################
2024-10-22 18:13:47 INFO -[ ]- ---------------- com.healerjean.proj.a_test.serializeable.utils.ProtostuffSerializer ---------------- com.healerjean.proj.a_test.serializeable.test.SerializerTest.testSerializer[58]
serialize result size: 141
deserialize result: SerializableBean{staticField='staticField', strVal='serializableBean', intVal=1, longVal=2, bigDecimal=999, date=Tue Oct 22 18:13:47 CST 2024, genericClazz=class com.caucho.hessian.io.SerializerFactory, interfaceClazz=interface com.healerjean.proj.a_test.serializeable.base.Serializer}
################
################
2024-10-22 18:13:47 INFO -[ ]- ---------------- com.healerjean.proj.a_test.serializeable.utils.MsgPackSerializer ---------------- com.healerjean.proj.a_test.serializeable.test.SerializerTest.testSerializer[58]
serialize result size: 197
deserialize result: SerializableBean{staticField='staticField', strVal='serializableBean', intVal=1, longVal=2, bigDecimal=999, date=Tue Oct 22 18:13:47 CST 2024, genericClazz=class com.caucho.hessian.io.SerializerFactory, interfaceClazz=interface com.healerjean.proj.a_test.serializeable.base.Serializer}
################
2、性能对比
JDK序列化结果字节数最大,Protostuff的结果最小。
| 序列化算法 | 序列化结果大小(字节) |
|---|---|
JDK |
852 |
Hessian |
298 |
Json |
221 |
Kryo |
167 |
Protostuff |
139 |
MsgPack |
195 |
压测后:
| 衡量维度 | 最好 | 最差 |
|---|---|---|
| 序列化结果大小 | Protostuff |
JDK |
| 序列化吞吐量 | Protostuff |
JDK |
| 反序列化吞吐量 | Kryo |
JDK |
| 综合吞吐量 | Kryo |
JDK |
| 框架 | 设计定位 | 序列化速度 | 数据体积 | 跨语言支持 | 兼容性 | 社区活跃度 | 易用性 | 典型适用场景 |
|---|---|---|---|---|---|---|---|---|
Kryo |
纯Java高性能序列化 |
极快 | 最小 | 否(仅Java) | 低(字段增减需处理) | 高 | 需管理线程安全/类注册 | 实时通信、游戏、内存缓存 |
Protostuff |
基于Protobuf的无预编译Java优化版 |
快 | 小 | 有限(主Java) | 中(末尾追加兼容) | 低 | 无需预编译,但需处理无参构造 | 微服务通信、持久化存储 |
Protobuf |
跨语言结构化数据协议 | 较快 | 小 | 是 | 高(版本兼容) | 高 | 修改Schema需重新生成代码,需预编译.proto文件 | 多语言RPC、API协议、长期数据存储 |
Hessian/Hessian2 |
跨语言动态兼容协议 | 中等/较快(接近Protobuf) |
大/中等 | 是 | 高(自动兼容) | 低 | 开箱即用,无复杂配置 | 遗留系统集成、简单跨语言调用 |
JSON(Jackson/fastjson/gson) |
可读性优先的文本序列化(其他为二进制,均不可读!) | 较慢 | 最大(文本) | 是 | 高(无结构约束) | 高 | 直接操作POJO,开发便捷 |
前后端交互、调试日志 |
MessagePack |
高效二进制跨语言序列化 | 快 | 小(紧凑) | 是 | 中(字段顺序敏感) | 中等活跃(持续更新) | 类似JSON,需处理二进制需POJO注解或预定义类 | 移动端、IoT设备、跨语言微服务 |
四、问题
1、hessian 和 msgpack 序列化方式区别
1) Hessian 序列化的时候,会写入字段名称,然后字段值,可以想象为一个 map。
2) MsgPack 序列化的时候,不写入字段名字,会按字段顺序写入值,可以想象为一个数组。
从这里可以看出:
1) Hessian 产生的数据包较大,MsgPack 产生的数据包较小。网络传输数据更小。
2) 序列化中 Hessian 的性能较差,(相当于每次 map 按名字取值),MsgPack 性能更佳,(相当于数组取值)
3) Hessian 的扩展性更好,上下兼容时,可以随意添加字段位置(相当于 map 可以随便赋值); MsgPack 的性能更佳,但是上下兼容时,需要保证字段顺序(包括枚举顺序)。
4) 还包括其它一些差异:
例如:Hessian 对 Map / List 等集合的支持就是全变成最普通的 Hashmap 或者 ArrayList ,一些指定的类型会丢失(例如LinkedHashMap –> HashMap),但是支持一些匿名的 Map / List等集合类;
而 MsgPack 会保留集合类的类型(例如 LinkedHashMap),但是不支持一些匿名集合类(例如 List.subList(),Map.keySet(),Collections.emptyList(),Guava 的匿名集合类,数据库查询结果直接返回的 list 等)。
2、为什么不推荐使用 JDK 自带的序列化?
1、不支持跨语言调用 : 如果调用的是其他语言开发的服务的时候就不支持了。
2、性能差 :相比于其他序列化框架性能更低,主要原因是序列化之后的字节数组体积较大,导致传输成本加大。
3、存在安全问题 :序列化和反序列化本身并不存在问题。但当输入的反序列化的数据可被用户控制,那么攻击者即可通过构造恶意输入,让反序列化产生非预期的对象,在此过程中执行构造的任意代码。
3、序列化过程中的注意事项
1)serialVersionUID:
serialVersionUID用于表示类的版本。它保证了在反序列化时,类的版本是兼容的。如果一个类发生了改变(比如增加或删除了字段),但是没有显式指定
serialVersionUID,反序列化时可能会抛出InvalidClassException异常。因此,最好为每个可序列化的类显式声明一个serialVersionUID。
private static final long serialVersionUID = 1L;
2)transient 关键字
序列化对象时,默认将里面所有的属性都进行序列化,除了
static、transient修饰的成员。如果某个字段不希望被序列化,可以使用transient关键字修饰。被transient修饰的字段在序列化时将被忽略。
3)对象的深拷贝
序列化不仅可以用于网络传输和持久化,还可以用于对象的深拷贝。将对象序列化为字节流后再反序列化回来,就得到了一个与原对象相互独立的副本。
4)如何避免敏感数据序列化
你可以使用
transient来避免敏感信息(如密码、身份证号)被序列化。在序列化时,这些信息将不会出现在序列化的字节流中。
5)序列化是否高效?
Java自带的序列化机制由于其实现的灵活性,有时性能不是最优的。在高性能场景下,你可能需要选择更加高效的序列化方案,如Protobuf、JSON、Kryo等。
6)static 和 transient 在序列化中的区别
| 定义 | 序列化过程 | 反序列化过程 | |
|---|---|---|---|
static |
static 关键字用来修饰类的变量或方法,这意味着它们是属于类本身的,而不是某个对象的实例。 |
在序列化过程中,被 static 修饰的变量不会被序列化。因此,序列化一个对象时,不会包含其类变量的值,因为类变量的值在 JVM 中是全局的,与特定的序列化实例无关。 |
在反序列化时,static 变量的值依然是类级别的当前状态,而不是从序列化的数据中获取。因为静态变量是共享的,因此它们与对象序列化或反序列化没有直接关联。 |
transient |
用于修饰类的成员变量,指明该变量在序列化时不应被序列化。 | 当一个对象被序列化时,transient修饰的变量则会被忽略。 |
在对象反序列化时,transient 变量会被初始化为它们的默认值。例如,数值类型会初始化为 0,对象类型会初始化为 null。序列化前设置的值将无法恢复。 |


