前言

Github:https://github.com/HealerJean

博客:http://blog.healerjean.com

一、什么是序列化/反序列化?

1、概念

⬤ 序列化是指将内存中的一个对象转化成字节序列(对象的数据、类型及对象中存储的属性),通俗点讲序列化就是冻结了那一刻内存中对象的状态,以便进行保存、程序间通信或者进行网络通信;

⬤ 反序列化就正好相反,将字节流转化成内存中的对象信息,将序列化冻结的对象状态进行解冻还原。

2、意义

序列化解决了如何在不同的环境(如不同的内存空间、不同的机器、不同的应用程序之间)以及不同的时间点(即现在和将来)有效、安全地传输和存储对象状态的问题。

它是现代软件开发中数据处理和交换的关键技术,特别是在分布式系统、网络通信、数据持久化和复杂对象管理中扮演着重要角色。

分布式系统:在 RPC(远程过程调用)中,客户端和服务端需要传递对象,序列化与反序列化在其中扮演了重要角色。

网络传输:通过网络传输对象时,序列化可以将对象转为字节流,便于传输。

对象数据持久化:在 Java 中,可以通过序列化将对象保存到磁盘中,比如将一个 Java 对象保存到文件中,然后在需要时恢复该对象。

缓存机制:将对象序列化后存储在缓存中,并在需要时通过反序列化来使用。

3、序列化的格式

序列化可以采取多种格式,包括但不限于:

二进制格式(如:HessianKryoProtobufProtoStuff 等):这种格式高效但不易读。

文本格式(如 JSONXML):序列化结果易读,便于调试,但通常比二进制格式占用空间大。

自定义格式:特定应用可能开发专用的序列化格式来优化性能或安全性。

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 序列化可以使用多种方式实现,如 fastjsonjacksongson,这里以 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、Kryojava 非线程安全)

Kryo 是一个快速序列化/反序列化工具,依赖于字节码生成机制(底层使用了 ASM 库),因此在序列化速度上有一定的优势,但正因如此,其使用也只能限制在基于 JVM 的语言上,Kryo 已经是一种非常成熟的序列化实现了,已经在 TwitterGrouponYahoo 以及多个著名开源项目(如 HiveStorm)中广泛的使用

Kryo 是专门针对 Java 语言序列化方式并且性能非常好,如果你的应用是专门针对 Java 语言的话可以考虑使用,并且 Dubbo 官网的一篇文章中提到说推荐使用 Kryo 作为生产环境的序列化方式

⚠️ 但是 Kryo 不是线程安全的,每个线程都应该有自己的 Kryo 对象、输入和输出实例。在多线程环境中,可以使用Kryo对象池解决这个问题。

关于 Kryo 的更多知识可参考 kryo githubKryo序列化

<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 出自于 Google,性能还比较优秀,也支持多种语言,同时还是跨平台的。就是在使用中过于繁琐,因为你需要自己定义 IDL 文件和生成对应的序列化代码。这样虽然不灵活,但是,另一方面导致 protobuf 没有序列化漏洞的风险。由于 Protobuf 的易用性,它的弟弟 Protostuff 诞生了。

protostuff 基于 Google 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、hessianmsgpack 序列化方式区别

1) Hessian 序列化的时候,会写入字段名称,然后字段值,可以想象为一个 map

2) MsgPack 序列化的时候,不写入字段名字,会按字段顺序写入值,可以想象为一个数组。

从这里可以看出:

1) Hessian 产生的数据包较大,MsgPack 产生的数据包较小。网络传输数据更小。

2) 序列化中 Hessian 的性能较差,(相当于每次 map 按名字取值),MsgPack 性能更佳,(相当于数组取值)

3) Hessian 的扩展性更好,上下兼容时,可以随意添加字段位置(相当于 map 可以随便赋值); MsgPack 的性能更佳,但是上下兼容时,需要保证字段顺序(包括枚举顺序)。

4) 还包括其它一些差异:

例如:HessianMap / 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 关键字

序列化对象时,默认将里面所有的属性都进行序列化,除了statictransient 修饰的成员。如果某个字段不希望被序列化,可以使用 transient 关键字修饰。被 transient 修饰的字段在序列化时将被忽略。

3)对象的深拷贝

序列化不仅可以用于网络传输和持久化,还可以用于对象的深拷贝。将对象序列化为字节流后再反序列化回来,就得到了一个与原对象相互独立的副本。

4)如何避免敏感数据序列化

你可以使用 transient来避免敏感信息(如密码、身份证号)被序列化。在序列化时,这些信息将不会出现在序列化的字节流中。

5)序列化是否高效?

Java 自带的序列化机制由于其实现的灵活性,有时性能不是最优的。在高性能场景下,你可能需要选择更加高效的序列化方案,如ProtobufJSONKryo 等。

6)static transient 在序列化中的区别

  定义 序列化过程 反序列化过程
static static 关键字用来修饰类的变量或方法,这意味着它们是属于类本身的,而不是某个对象的实例。 在序列化过程中,被 static 修饰的变量不会被序列化。因此,序列化一个对象时,不会包含其类变量的值,因为类变量的值在 JVM 中是全局的,与特定的序列化实例无关。 在反序列化时,static 变量的值依然是类级别的当前状态,而不是从序列化的数据中获取。因为静态变量是共享的,因此它们与对象序列化或反序列化没有直接关联
transient 用于修饰类的成员变量,指明该变量在序列化时不应被序列化。 当一个对象被序列化时,transient修饰的变量则会被忽略。 在对象反序列化时,transient 变量会被初始化为它们的默认值。例如,数值类型会初始化为 0,对象类型会初始化为 null。序列化前设置的值将无法恢复。

ContactAuthor