Java-IO流之序列化与反序列化详解
- 一、序列化与反序列化概述
- 1.1 基本概念
- 1.2 核心接口与类
- 1.3 应用场景
- 二、Java序列化的基本实现
- 2.1 实现Serializable接口
- 2.2 使用ObjectOutputStream进行序列化
- 2.3 使用ObjectInputStream进行反序列化
- 三、序列化的高级特性
- 3.1 serialVersionUID的作用
- 3.2 瞬态关键字transient
- 3.3 自定义序列化方法
- 3.4 实现Externalizable接口
- 四、序列化的最佳实践
- 4.1 始终声明serialVersionUID
- 4.2 处理敏感字段
- 4.3 实现readObjectNoData方法
- 4.4 序列化单例和枚举
- 4.5 序列化集合和数组
- 五、序列化的常见问题与解决方案
- 5.1 NotSerializableException
- 5.2 InvalidClassException
- 5.3 性能问题
- 5.4 安全风险
- 六、替代序列化方案
- 6.1 JSON序列化
- 6.2 Protobuf序列化
- 6.3 Kryo序列化
Java中对象的序列化(Serialization)与反序列化(Deserialization)是一项重要技术,为对象的持久化和远程传输提供了基础支持,它允许将对象转换为字节流以便存储或传输,也可以将字节流还原为原始对象。这项技术在分布式系统、远程方法调用(RMI)、缓存机制等场景中有着广泛的应用。本文我将深入探讨Java序列化与反序列化的原理、实现方法及最佳实践,带你全面掌握这一核心技术。
一、序列化与反序列化概述
1.1 基本概念
- 序列化(Serialization):将Java对象转换为字节流的过程
- 反序列化(Deserialization):将字节流恢复为Java对象的过程
1.2 核心接口与类
- Serializable接口:标记接口,实现该接口的类可以被序列化
- Externalizable接口:继承自Serializable,提供更细粒度的序列化控制
- ObjectOutputStream:用于将对象写入输出流
- ObjectInputStream:用于从输入流读取对象
1.3 应用场景
- 对象持久化:将对象保存到文件或数据库
- 远程通信:在网络中传输对象
- 缓存机制:将对象缓存到内存或磁盘
- 分布式系统:在不同节点间传递对象
二、Java序列化的基本实现
2.1 实现Serializable接口
要使一个类可序列化,只需实现java.io.Serializable
接口(该接口是一个标记接口,没有方法)。
import java.io.Serializable;
public class Person implements Serializable {
private static final long serialVersionUID = 1L;
private String name;
private int age;
public Person(String name, int age) {
this.name = name;
this.age = age;
}
// Getters and setters
public String getName() { return name; }
public int getAge() { return age; }
@Override
public String toString() {
return "Person{name='" + name + "', age=" + age + "}";
}
}
2.2 使用ObjectOutputStream进行序列化
import java.io.*;
public class SerializationExample {
public static void main(String[] args) {
Person person = new Person("张三", 30);
try (ObjectOutputStream oos = new ObjectOutputStream(
new FileOutputStream("person.ser"))) {
// 序列化对象
oos.writeObject(person);
System.out.println("对象序列化成功");
} catch (IOException e) {
e.printStackTrace();
}
}
}
2.3 使用ObjectInputStream进行反序列化
import java.io.*;
public class DeserializationExample {
public static void main(String[] args) {
try (ObjectInputStream ois = new ObjectInputStream(
new FileInputStream("person.ser"))) {
// 反序列化对象
Person person = (Person) ois.readObject();
System.out.println("对象反序列化成功");
System.out.println(person);
} catch (IOException | ClassNotFoundException e) {
e.printStackTrace();
}
}
}
三、序列化的高级特性
3.1 serialVersionUID的作用
serialVersionUID
是一个标识序列化类版本的静态常量,用于在反序列化时验证类的兼容性。如果不指定,Java会根据类的结构自动生成一个,但建议显式声明以避免版本不一致问题。
private static final long serialVersionUID = 1L;
3.2 瞬态关键字transient
使用transient
关键字修饰的字段不会被序列化,反序列化后该字段的值为默认值。
import java.io.Serializable;
public class User implements Serializable {
private static final long serialVersionUID = 1L;
private String username;
private transient String password; // 密码字段不被序列化
public User(String username, String password) {
this.username = username;
this.password = password;
}
// Getters and setters
public String getUsername() { return username; }
public String getPassword() { return password; }
@Override
public String toString() {
return "User{username='" + username + "', password='" + password + "'}";
}
}
3.3 自定义序列化方法
可以通过在类中定义以下两个特殊方法来自定义序列化过程:
private void writeObject(ObjectOutputStream out)
private void readObject(ObjectInputStream in)
import java.io.*;
public class CustomSerialization implements Serializable {
private static final long serialVersionUID = 1L;
private int value;
public CustomSerialization(int value) {
this.value = value;
}
// 自定义序列化方法
private void writeObject(ObjectOutputStream out) throws IOException {
// 写入原始值的加密版本
out.writeInt(value * 2);
}
// 自定义反序列化方法
private void readObject(ObjectInputStream in) throws IOException, ClassNotFoundException {
// 读取加密值并解密
value = in.readInt() / 2;
}
public int getValue() {
return value;
}
}
3.4 实现Externalizable接口
Externalizable
接口继承自Serializable
,提供了更细粒度的序列化控制。实现该接口需要重写writeExternal
和readExternal
方法。
import java.io.*;
public class Employee implements Externalizable {
private static final long serialVersionUID = 1L;
private String name;
private int employeeId;
// 必须提供无参构造函数
public Employee() {}
public Employee(String name, int employeeId) {
this.name = name;
this.employeeId = employeeId;
}
@Override
public void writeExternal(ObjectOutput out) throws IOException {
out.writeObject(name);
out.writeInt(employeeId);
}
@Override
public void readExternal(ObjectInput in) throws IOException, ClassNotFoundException {
name = (String) in.readObject();
employeeId = in.readInt();
}
@Override
public String toString() {
return "Employee{name='" + name + "', employeeId=" + employeeId + "}";
}
}
四、序列化的最佳实践
4.1 始终声明serialVersionUID
为每个可序列化的类显式声明serialVersionUID
,以确保版本兼容性。
4.2 处理敏感字段
使用transient
关键字标记敏感字段,避免在序列化过程中泄露信息。
4.3 实现readObjectNoData方法
在类中实现readObjectNoData()
方法,以处理反序列化时没有数据的情况。
private void readObjectNoData() throws ObjectStreamException {
// 初始化对象的默认状态
this.name = "默认名称";
this.age = 0;
}
4.4 序列化单例和枚举
对于单例类,应确保反序列化不会创建新的实例,可以通过实现readResolve()
方法:
private Object readResolve() throws ObjectStreamException {
return Singleton.getInstance();
}
枚举类型本身就支持序列化,无需特殊处理。
4.5 序列化集合和数组
集合和数组如果包含可序列化的元素,则它们本身也是可序列化的。
import java.io.*;
import java.util.ArrayList;
import java.util.List;
public class CollectionSerializationExample {
public static void main(String[] args) {
List<Person> people = new ArrayList<>();
people.add(new Person("张三", 30));
people.add(new Person("李四", 40));
try (ObjectOutputStream oos = new ObjectOutputStream(
new FileOutputStream("people.ser"))) {
oos.writeObject(people);
} catch (IOException e) {
e.printStackTrace();
}
try (ObjectInputStream ois = new ObjectInputStream(
new FileInputStream("people.ser"))) {
@SuppressWarnings("unchecked")
List<Person> deserializedPeople = (List<Person>) ois.readObject();
for (Person person : deserializedPeople) {
System.out.println(person);
}
} catch (IOException | ClassNotFoundException e) {
e.printStackTrace();
}
}
}
五、序列化的常见问题与解决方案
5.1 NotSerializableException
当尝试序列化未实现Serializable
接口的类时,会抛出此异常。解决方案是确保所有需要序列化的类都实现Serializable
接口。
5.2 InvalidClassException
当序列化版本不一致或类结构发生变化时,可能会抛出此异常。解决方案是显式声明serialVersionUID
,并在类结构变化时谨慎处理。
5.3 性能问题
Java原生序列化性能较低,特别是对于大量数据。可以考虑使用更高效的序列化框架,如JSON、Protobuf、Kryo等。
5.4 安全风险
反序列化不受信任的数据可能导致安全漏洞,如远程代码执行。应避免反序列化来自不可信源的数据,或使用安全的序列化框架。
六、替代序列化方案
6.1 JSON序列化
JSON是一种轻量级的数据交换格式,广泛用于Web应用中。可以使用Jackson、Gson等库实现Java对象与JSON的互转。
import com.fasterxml.jackson.databind.ObjectMapper;
public class JsonSerializationExample {
public static void main(String[] args) throws Exception {
ObjectMapper mapper = new ObjectMapper();
Person person = new Person("张三", 30);
// 对象转JSON
String json = mapper.writeValueAsString(person);
System.out.println("JSON: " + json);
// JSON转对象
Person deserializedPerson = mapper.readValue(json, Person.class);
System.out.println("对象: " + deserializedPerson);
}
}
6.2 Protobuf序列化
Protobuf是Google开发的高效序列化框架,具有高性能和小体积的特点。
// 定义.proto文件
syntax = "proto3";
message Person {
string name = 1;
int32 age = 2;
}
// 使用Protobuf生成的类进行序列化和反序列化
PersonProto.Person person = PersonProto.Person.newBuilder()
.setName("张三")
.setAge(30)
.build();
// 序列化
byte[] data = person.toByteArray();
// 反序列化
PersonProto.Person deserializedPerson = PersonProto.Person.parseFrom(data);
6.3 Kryo序列化
Kryo是一个快速高效的Java序列化框架,支持自定义序列化策略。
import com.esotericsoftware.kryo.Kryo;
import com.esotericsoftware.kryo.io.Input;
import com.esotericsoftware.kryo.io.Output;
public class KryoSerializationExample {
public static void main(String[] args) {
Kryo kryo = new Kryo();
kryo.register(Person.class);
Person person = new Person("张三", 30);
// 序列化
try (Output output = new Output(new FileOutputStream("person.kryo"))) {
kryo.writeObject(output, person);
} catch (IOException e) {
e.printStackTrace();
}
// 反序列化
try (Input input = new Input(new FileInputStream("person.kryo"))) {
Person deserializedPerson = kryo.readObject(input, Person.class);
System.out.println(deserializedPerson);
} catch (IOException e) {
e.printStackTrace();
}
}
}
若这篇内容帮到你,动动手指支持下!关注不迷路,干货持续输出!
ヾ(´∀ ˋ)ノヾ(´∀ ˋ)ノヾ(´∀ ˋ)ノヾ(´∀ ˋ)ノヾ(´∀ ˋ)ノ