前言

Github:https://github.com/HealerJean

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

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

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 已经是一种非常成熟的序列化实现了,已经在 Twitter、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 出自于 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、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。序列化前设置的值将无法恢复。

ContactAuthor