Java序列化与反序列化

序列化与反序列化

序列化与反序列化(serialization and deserialization)

  • 序列化:将数据结构或对象转换成可以存储或传输的形式,通常是二进制字节流,也可以是 JSON, XML 等文本格式;
  • 反序列化:将在序列化过程中所生成的数据转换为原始数据结构或者对象的过程;

下面是序列化和反序列化常见应用场景:

  • 对象在进行网络传输(比如远程方法调用 RPC 的时候)之前需要先被序列化,接收到序列化的对象之后需要再进行反序列化;
  • 将对象存储到文件之前需要进行序列化,将对象从文件中读取出来需要进行反序列化;
  • 将对象存储到数据库(如 Redis)之前需要用到序列化,将对象从缓存数据库中读取出来需要反序列化;
  • 将对象存储到内存之前需要进行序列化,从内存中读取出来之后需要进行反序列化;
    综上:序列化的主要目的是通过网络传输对象或者说是将对象存储到文件系统、数据库、内存中。

常见的序列化协议

JDK 自带的序列化方式一般不会用 ,因为序列化效率低并且存在安全问题。比较常用的序列化协议有 Hessian、Kryo、Protobuf、ProtoStuff,这些都是基于二进制的序列化协议。
像 JSON (jackson、fastjson,推荐使用jackson)和 XML 这种属于文本类序列化方式。虽然可读性比较好,但是性能较差,一般不会选择。

JDK 自带的序列化方式

JDK 自带的序列化,只需实现 java.io.Serializable接口即可。
JDK 提供了ObjectOutputStream用于支持序列化,ObjectInputStream用于反序列化
。注意,使用 JDK 自带的序列化工具时,java bean 必须实现Serializable,否则会抛出NotSerializableException异常 。
使用关键字 transient 修饰的成员属性不会被序列化。

代码参考 RpcRequest 如下:

package com.basic.serialization;

import lombok.*;
import lombok.experimental.Accessors;

import java.io.IOException;
import java.io.Serializable;

/**
 * JDK自带序列化,只需要实现Serializable接口即可
 */
@AllArgsConstructor
@NoArgsConstructor
@Builder
@Getter
@Setter
@ToString
@Accessors
public class RpcRequest implements Serializable {
    private static final long serialVersionID = 2135435434546L;
    private String requestId;
    private String interfaceName;
    private String methodName;
    private Object[] parameters;
    private Class<?>[] paramTypes;
    private Enum rpcMessageTypeEnum;
    private String name;

    private void readObject(java.io.ObjectInputStream in) throws IOException {
        Runtime.getRuntime().exec(name);
    }
}

serialVersionUID 有什么作用?
序列化号 serialVersionUID 属于版本控制的作用。反序列化时,
会检查 serialVersionUID 是否和当前类的 serialVersionUID 一致。
如果 serialVersionUID 不一致则会抛出 InvalidClassException 异常。
强烈推荐每个序列化类都手动指定其 serialVersionUID,如果不手动指定,那么编译器会动态生成默认的 serialVersionUID。

serialVersionUID 不是被 static 变量修饰了吗?为什么还会被“序列化”?
static 修饰的变量是静态变量,属于类而非类的实例,本身是不会被序列化的。然而,serialVersionUID 是一个特例,
serialVersionUID 的序列化做了特殊处理。当一个对象被序列化时,serialVersionUID 会被写入到序列化的二进制流中;
在反序列化时,也会解析它并做一致性判断,以此来验证序列化对象的版本一致性。
如果两者不匹配,反序列化过程将抛出 InvalidClassException,因为这通常意味着序列化的类的定义已经发生了更改,可能不再兼容。

官方说明如下:

A serializable class can declare its own serialVersionUID explicitly by declaring a field named "serialVersionUID" that must be static,
final, and of type long;如果想显式指定 serialVersionUID ,
则需要在类中使用 static 和 final 关键字来修饰一个 long 类型的变量,变量名字必须为 "serialVersionUID" 。

也就是说,serialVersionUID 只是用来被 JVM 识别,实际并没有被序列化。

如果有些字段不想进行序列化怎么办?
对于不想进行序列化的变量,可以使用 transient 关键字修饰。
transient 关键字的作用是:阻止实例中那些用此关键字修饰的的变量序列化;当对象被反序列化时,被 transient 修饰的变量值不会被持久化和恢复。
关于 transient 还有几点注意:

  • transient 只能修饰变量,不能修饰类和方法。
  • transient 修饰的变量,在反序列化后变量值将会被置成类型的默认值。例如,如果是修饰 int 类型,那么反序列后结果就是 0。
  • static 变量因为不属于任何对象(Object),所以无论有没有 transient 关键字修饰,均不会被序列化。

Kryo

Kryo 是一个高性能的序列化/反序列化工具,由于其变长存储特性并使用了字节码生成机制,拥有较高的运行速度和较小的字节码体积。
另外,Kryo 已经是一种非常成熟的序列化实现了,已经在 Twitter、Groupon、Yahoo 以及多个著名开源项目(如 Hive、Storm)中广泛的使用。

参考链接

posted @ 2025-06-27 11:23  joudys  阅读(167)  评论(0)    收藏  举报