Java序列化与反序列化
一、什么是序列化和反序列化
序列化(Serialization) 和 反序列化(Deserialization) 是计算机科学中数据处理的核心概念,主要用于数据的 存储 、传输和状态恢复 。
简单来说:
- 序列化 :将内存中的对象/数据结构转换为可存储或传输的格式(如字节流、JSON字符串)。
- 反序列化:将存储或传输的格式还原为内存中的对象/数据结构。
二、序列化有哪些使用场景
1. 网络传输 — 最经典的是RPC调用
比如Dubbo这样的框架,客户端调用远程服务时,需要把方法参数(各种Java对象)通过网络传给服务端,结果也要传回来。这就必须先把对象序列化成字节流,到了对端再反序列化回来。
2. 持久化存储
把对象存到文件或数据库里,下次程序启动时再恢复。比如保存用户会话状态、游戏存档、复杂配置等。Redis缓存Java对象时,也常先把对象序列化再存储。
3. HTTP会话管理
Web应用集群环境下,用户Session需要在多台服务器间同步。Tomcat等容器会把Session对象序列化后复制到其他节点,或者存到外部Redis中,确保用户不管被分配到哪台机器,都能拿到自己的会话。
4. 深拷贝
想完全复制一个对象(包括它引用的所有子对象),一种简便方式就是先序列化再反序列化,能得到一个全新的独立副本。不过性能不高,一般不推荐在频繁调用的业务代码里这样用。
5. 消息队列
往RabbitMQ、Kafka发消息时,消息体如果是对象,就需要序列化。常见做法是序列化成JSON字符串或字节数组再发送。
三、序列化协议有哪些
常用的序列化协议有Hessian2、Protobuf、Kryo等,相对而言,Java原生序列化不仅效率低并且还存在安全隐患。下面我们通过代码来看看它们具体该如何使用:
一、Java原生序列化
使用Java原生序列化我们需要让需要序列化的类实现 java.io.Serializable接口。
import java.io.Serializable;
public class User implements Serializable {
// 强烈建议显式声明,避免类变更后反序列化失败
private static final long serialVersionUID = 1L;
private String name;
private int age;
// transient 修饰的字段不会被序列化
private transient String password;
// 静态变量属于类,不属于对象,不会被序列化
private static String platform = "Android";
public User(String name, int age, String password) {
this.name = name;
this.age = age;
this.password = password;
}
@Override
public String toString() {
return "User{name='" + name + "', age=" + age +
", password='" + password + "', platform='" + platform + "'}";
}
}
public class Code01 {
public static void main(String[] args) {
//1.序列化:对象--》文件
User user = new User("zs",23,"123456");
try(FileOutputStream fos = new FileOutputStream("user.ser");
ObjectOutputStream oos = new ObjectOutputStream(fos)){
oos.writeObject(user);
}catch (IOException e){
e.printStackTrace();
}
}
}
public class Code01 {
public static void main(String[] args) {
//2.反序列化:文件--》对象
User user = null;
try(FileInputStream fis = new FileInputStream("user.ser");
ObjectInputStream ois = new ObjectInputStream(fis)) {
user = (User) ois.readObject();
//反序列化结果:User{name='zs', age=23, password='null', platform='Android'}
System.out.println("反序列化结果:"+user);
} catch (IOException | ClassNotFoundException e) {
e.printStackTrace();
}
}
}
需要注意的是:Java反序列化有个著名的安全问题。反序列化时会调用 readObject、readResolve等方法,如果字节流是恶意构造的,就能在反序列化时执行任意代码。历史上Log4j2、WebLogic等都爆出过相关高危漏洞。
二、Kryo
Kryo是一个高性能的 Java 序列化框架,常用于需要高效序列化/反序列化的场景(如分布式缓存、RPC 通信、数据存储等)。Kryo比原生序列化快很多,而且更省空间。下面来看demo示例:
<dependency>
<groupId>com.esotericsoftware</groupId>
<artifactId>kryo</artifactId>
<version>5.6.0</version>
</dependency>
public class User {
private String name;
private int age;
private String password; // 不需要 transient,Kryo 有自己的控制方式
// 必须有无参构造器,否则 Kryo 无法创建实例
public User() {}
public User(String name, int age, String password) {
this.name = name;
this.age = age;
this.password = password;
}
@Override
public String toString() {
return "User{name='" + name + "', age=" + age + ", password='" + password + "'}";
}
}
import com.esotericsoftware.kryo.Kryo;
import com.esotericsoftware.kryo.io.Input;
import com.esotericsoftware.kryo.io.Output;
import java.io.*;
public class KryoBasicDemo {
public static void main(String[] args) {
User user = new User("张三", 25, "123456");
// 1. 序列化
byte[] bytes = serialize(user);
System.out.println("序列化字节数:" + bytes.length);
// 2. 反序列化
User restored = deserialize(bytes, User.class);
System.out.println("反序列化结果:" + restored);
}
// 序列化对象到字节数组
public static byte[] serialize(Object obj) {
Kryo kryo = new Kryo();
// 注册类可提升性能,不注册也能用但会写入完整类名
kryo.register(User.class);
try (ByteArrayOutputStream baos = new ByteArrayOutputStream();
Output output = new Output(baos)) {
kryo.writeObject(output, obj);
output.flush();
return baos.toByteArray();
} catch (IOException e) {
throw new RuntimeException("序列化失败", e);
}
}
// 从字节数组反序列化
public static <T> T deserialize(byte[] bytes, Class<T> clazz) {
Kryo kryo = new Kryo();
kryo.register(clazz);
try (Input input = new Input(new ByteArrayInputStream(bytes))) {
return kryo.readObject(input, clazz);
}
}
}
需要注意的是我们在实际项目里绝不能每次序列化都 new Kryo()(开销很大),也不应该多线程共用一个 Kryo 实例。这里我们可以借助 ThreadLocal来做一层封装:
import com.esotericsoftware.kryo.Kryo;
import com.esotericsoftware.kryo.io.Input;
import com.esotericsoftware.kryo.io.Output;
import java.io.ByteArrayInputStream;
import java.io.ByteArrayOutputStream;
import java.io.IOException;
public class KryoThreadLocal {
private static final ThreadLocal<Kryo> kryoLocal = ThreadLocal.withInitial(() -> {
Kryo kryo = new Kryo();
kryo.setRegistrationRequired(false);
// 可以在这里提前注册常用类
kryo.register(User.class);
kryo.register(java.util.ArrayList.class);
kryo.register(java.util.HashMap.class);
return kryo;
});
public static Kryo get() {
return kryoLocal.get();
}
public static <T> byte[] serialize(T obj) {
Kryo kryo = get();
try (ByteArrayOutputStream baos = new ByteArrayOutputStream();
Output output = new Output(baos)) {
kryo.writeObject(output, obj);
output.flush();
return baos.toByteArray();
} catch (IOException e) {
throw new RuntimeException(e);
}
}
public static <T> T deserialize(byte[] bytes, Class<T> clazz) {
Kryo kryo = get();
try (Input input = new Input(new ByteArrayInputStream(bytes))) {
return kryo.readObject(input, clazz);
}
}
}
三、Protobuf
Protobuf(Protocol Buffers)是 Google 出品的一种跨语言序列化方案。和 Kryo 最大的不同是,它需要先定义 .proto 文件,然后生成代码,最后才能序列化。这样做的好处是跨语言、体积小、有 schema 校验。
<dependencies>
<dependency>
<groupId>com.google.protobuf</groupId>
<artifactId>protobuf-java</artifactId>
<version>3.25.3</version>
</dependency>
</dependencies>
<build>
<plugins>
<!-- 代码生成插件,在 compile 阶段自动把 .proto 生成 Java 类 -->
<plugin>
<groupId>org.xolstice.maven.plugins</groupId>
<artifactId>protobuf-maven-plugin</artifactId>
<version>0.6.1</version>
<configuration>
<protocArtifact>com.google.protobuf:protoc:3.25.3:exe:${os.detected.classifier}</protocArtifact>
<!-- 自定义 proto 源文件目录,可以有多个 -->
<protoSourceRoot>${project.basedir}/src/main/resources/proto</protoSourceRoot>
</configuration>
<executions>
<execution>
<goals><goal>compile</goal></goals>
</execution>
</executions>
</plugin>
<!-- 用于自动检测操作系统,配合上面的 ${os.detected.classifier} -->
<plugin>
<groupId>kr.motd.maven</groupId>
<artifactId>os-maven-plugin</artifactId>
<version>1.7.1</version>
<executions>
<execution>
<phase>initialize</phase>
<goals><goal>detect</goal></goals>
</execution>
</executions>
</plugin>
</plugins>
</build>
接下来是编写 user.proto文件,注意其存放目录要和上面配置的一致!!
syntax = "proto3";
// 包名,生成的 Java 类会放在这个包下
package com.hzp.protobuf;
// 如果想让生成的 Java 类名加前缀或放在外层类里,可以设这个
option java_multiple_files = true;
// 消息定义——这就是你要序列化的对象
message User {
string name = 1; // =1 是字段编号,不是默认值
int32 age = 2;
string email = 3;
// 枚举示例
enum Gender {
UNKNOWN = 0;
MALE = 1;
FEMALE = 2;
}
Gender gender = 4;
// 嵌套消息示例
message Address {
string city = 1;
string street = 2;
}
Address address = 5;
// repeated 表示集合(List)
repeated string hobbies = 6;
}
然后就是执行 mvn compile,会在 target/generated-sources下面生成对应的java类。如果我们想使用生成的java类,我们还需要将 protobuf/java标记为源代码根目录,这样就不用拷贝到我们手动编写的代码里了。

最后就是编写一个demo测试一下:
import com.google.protobuf.InvalidProtocolBufferException;
public class ProtobufDemo {
public static void main(String[] args) throws InvalidProtocolBufferException {
// 1. 构建对象(使用 Builder 模式)
User.Address address = User.Address.newBuilder()
.setCity("北京")
.setStreet("长安街")
.build();
User user = User.newBuilder()
.setName("张三")
.setAge(25)
.setEmail("zhangsan@example.com")
.setGender(User.Gender.MALE)
.setAddress(address)
.addHobbies("篮球")
.addHobbies("编程")
.build();
System.out.println("原始对象:\n" + user);
// 2. 序列化为字节数组
byte[] bytes = user.toByteArray();
System.out.println("序列化字节数:" + bytes.length);
// 3. 反序列化
User restored = User.parseFrom(bytes);
System.out.println("\n反序列化结果:\n" + restored);
// 4. 验证字段
System.out.println("\n姓名:" + restored.getName());
System.out.println("地址:" + restored.getAddress().getCity());
System.out.println("爱好数量:" + restored.getHobbiesCount());
System.out.println("第一个爱好:" + restored.getHobbies(0));
}
}
四、ProtoStuff
我们发现,上面的这个Protobuf好像有点麻烦,用起来不太顺手,于是它的“升级版”ProtoStuff就诞生了,ProtoStuff基于上面的Protobuf提供了更多的功能和更加简单的用法。下面我们来看一下它的具体用法:
<dependencies>
<dependency>
<groupId>io.protostuff</groupId>
<artifactId>protostuff-core</artifactId>
<version>1.8.0</version>
</dependency>
<dependency>
<groupId>io.protostuff</groupId>
<artifactId>protostuff-runtime</artifactId>
<version>1.8.0</version>
</dependency>
<dependency>
<groupId>org.projectlombok</groupId>
<artifactId>lombok</artifactId>
<version>1.18.46</version>
</dependency>
</dependencies>
import lombok.AllArgsConstructor;
import lombok.Builder;
import lombok.Data;
import lombok.NoArgsConstructor;
@Data
@NoArgsConstructor
@AllArgsConstructor
@Builder
public class User {
private String name;
private int age;
// 如果你不想序列化某些字段,用 transient 关键字
private transient String password;
}
import io.protostuff.LinkedBuffer;
import io.protostuff.ProtostuffIOUtil;
import io.protostuff.Schema;
import io.protostuff.runtime.RuntimeSchema;
public class ProtostuffDemo {
public static void main(String[] args) {
// 1. 准备对象
User user = User.builder()
.name("张三")
.age(25)
.build();
// 2. 获取运行时动态生成的 Schema(相当于 .proto 文件的作用)
Schema<User> schema = RuntimeSchema.getSchema(User.class);
// 3. 序列化
LinkedBuffer buffer = LinkedBuffer.allocate(512);
byte[] bytes;
try {
bytes = ProtostuffIOUtil.toByteArray(user, schema, buffer);
} finally {
buffer.clear();
}
System.out.println("序列化字节数: " + bytes.length);
// 4. 反序列化
User restored = schema.newMessage(); // 创建空对象
ProtostuffIOUtil.mergeFrom(bytes, restored, schema);
System.out.println("反序列化结果: " + restored.getName());
}
}
五、横向对比
| 对比维度 | Java 原生序列化 | Kryo | Protobuf (原生) | Protostuff |
|---|---|---|---|---|
| 核心机制 | JDK内置,实现 Serializable接口 |
基于字节码生成,直接操作字段 | IDL定义 .proto 文件,编译生成代码 |
运行时动态生成Schema,操作POJO |
| 易用性 | 最简单实现接口即可 | 简单POJO无需特殊处理 | 最麻烦需学IDL,多步生成和配置 | 较简单操作POJO,无需代码生成 |
| 序列化格式 | 专有二进制格式 | 专有二进制格式 | 标准二进制格式跨语言通用 | 标准Protobuf二进制格式 |
| 序列化速度 | 最慢,大量反射,I/O未优化 | 极快,字节码直接操作,接近极限 | 很快,预编译生成高效代码 | 很快,运行时动态生成,略慢于Kryo |
| 序列化体积 | 庞大,携带完整类元信息 | 较小 | 极小,字段变长编码,紧凑高效 | 较小,与原生Protobuf相当 |
| 跨语言支持 | 仅Java反序列化依赖JDK | 仅Java | 原生支持C++, Python, Go等 | 理论上支持但实际仅限于Java |
| 向后兼容性 | 弱,类结构变化易失败 | 极弱,字段增删易出错 | 极强,字段编解码安全升级 | 弱,依赖字段顺序或注解 |
| 线程安全性 | 线程安全 | 线程不安全需Pool或ThreadLocal | 线程安全生成的消息对象不可变 | 线程安全 |
| 安全性 | 极低,是反序列化漏洞重灾区 | 较安全,比原生好,但非强项 | 高,Schema强约束,防篡改 | 高,基于Schema,防恶意类加载 |
| 适用场景 | 仅遗留系统维护 | 纯Java高性能缓存、RPC | 跨语言通信、长期存储、大型微服务API | 纯Java,想要Protobuf格式但不想写 .proto |

浙公网安备 33010602011771号