Java中的序列化(Serialization)和反序列化(Deserialization)是将对象和字节流之间进
行相互转换的过程。这两个过程用于保存对象的状态并能够在需要时恢复这些状态。
一、理解序列化和反序列化
概念
序列化:序列化是将对象的状态转换为字节流的过程,以便可以将该对象保存到文件、数据库,或通过网络进行传输。序列化使得对象的生命周期可以超越JVM的运行时间,并且可以在不同的机器之间传输。
反序列化:反序列化是将字节流恢复为对象的过程。通过反序列化,可以从序列化的字节流中重新创建对象的实例。
Java序列化的优点
1. 对象持久化:
可以将对象的状态保存到文件、数据库等持久化存储中,以便在以后可以恢复和使用。
2. 对象传输:
在分布式系统中,可以通过网络传输对象。序列化将对象转换为字节流,使得对象可以通过网络传输到其他Java虚拟机上。
3. 缓存:
将对象序列化后保存在缓存中,以提高系统性能。反序列化时可以快速恢复对象,而不需要重新创建。
4. 深拷贝:
通过序列化和反序列化可以实现对象的深拷贝。将对象序列化后再反序列化,得到的是一个全新的对象,所有引用类型的成员变量也会被拷贝。
5. 会话管理:
在Web应用中,可以将用户会话信息序列化到文件或数据库中,以便在服务器重启后恢复用户会话。
二、序列化的实现原理
1. 实现 Serializable 接口:
一个类必须实现 java.io.Serializable 接口才能使其对象序列化。
该接口是一个标记接口(没有方法),其目的是告诉JVM该类的对象可以被序列化。
2. ObjectOutputStream:
使用 ObjectOutputStream 类来将对象写入到输出流中。
调用 writeObject(Object obj) 方法将对象转换为字节流。
3. serialVersionUID:
用于版本控制。确保在反序列化时,加载的类与序列化的类是兼容的。
如果没有定义 serialVersionUID ,Java会根据类的细节自动生成一个,但是这个自动生成的ID在类的定义发生变化时可能会变化,从而导致反序列化失败。
建议显式声明 serialVersionUID 例如:
private static final long serialVersionUID = 1L;
三、API 介绍
API是Java平台提供的一系列接口和类,用于支持对象的序列化和反序列化。
基本概念与用法
Serializable接口:为实现序列化的类提供了标记。任何需要被序列化的类都必须实现这个接口。
ObjectOutputStream类:这是用于序列化的主要类。它提供了writeObject方法,能够将对象写入输出流,从而完成序列化过程。
ObjectInputStream类:这个类用于从输入流中读取字节序列并将其反序列化为对象。它通过readObject方法实现这一功能。
自定义序列化
如果需要自定义序列化过程,可以在类中定义 writeObject 和 readObject 方法,
这时候序列化与反序列化会调用这些方法而不是默认方法,从而允许开发者对序列化过程进行更精细的控制。
private void writeObject(ObjectOutputStream out) throws IOException { // 自定义序列化逻辑 } private void readObject(ObjectInputStream in) throws IOException, ClassNotFoundException { // 自定义反序列化逻辑 }
transient关键字
在Java中,transient关键字用于标识某个类的字段不需要进行序列化。具体来说,使用transient修饰的字段在对象序列化时不会被保存到字节流中,从而也不会在反序列化时恢复该字段的值。
transient 关键字的使用场景
敏感数据:
对于包含敏感信息的字段,如密码、信用卡信息等,使用transient关键字可以避免在序列化过程中泄露这些信息。
冗余数据:
如果某些字段的值是可以计算或重新生成的,可以将这些字段标记为transient以减少序列化数据的大小。
非持久化状态:
某些字段只用于临时计算或逻辑控制,不需要被序列化和持久化。例如,缓存数据、线程相关状态等。
transient 关键字的使用
import java.io.*; class User implements Serializable { private static final long serialVersionUID = 1L; String username; transient String password; // 不会被序列化 public User(String username, String password) { this.username = username; this.password = password; } }
四、实现Java序列化的三种方法
使用Serializable接口:这是Java提供的标准序列化机制。要实现对象的序列化,需要让对象所属的类实现Serializable接口,并使用ObjectOutputStream类的writeObject方法将对象写入到输出流中。反序列化时,可以使用ObjectInputStream类的readObject方法从输入流中读取字节序列并将其反序列化为对象。
使用Externalizable接口:与Serializable接口相比,Externalizable接口提供了更精细的控制,允许开发者自定义序列化和反序列化的过程。要实现对象的序列化,需要让对象所属的类实现Externalizable接口,并实现writeExternal方法和readExternal方法。这两个方法分别用于将对象转换为字节流以及从字节流中恢复对象。
使用JSON序列化:JSON是一种轻量级的数据交换格式,可以方便地将Java对象转换为JSON字符串,并将JSON字符串转换回Java对象。在Java中,可以使用第三方库如Jackson、Gson等来实现JSON序列化。通过这些库提供的API,可以将Java对象转换为JSON字符串,并将JSON字符串转换回Java对象。
五、代码示例
1. 序列化示例
import java.io.FileOutputStream; import java.io.ObjectOutputStream; import java.io.Serializable; // 定义一个实现Serializable接口的Employee类,用于对象的序列化和反序列化 class Employee implements Serializable { // serialVersionUID用于版本控制,确保序列化和反序列化过程中类的兼容性 private static final long serialVersionUID = 1L; // 员工的姓名 String name; // 员工的ID int id; // 构造函数,初始化name和id字段 public Employee(String name, int id) { this.name = name; this.id = id; } } // 序列化示例类 public class SerializeExample { public static void main(String[] args) { // 创建一个Employee对象 Employee emp = new Employee("John Doe", 12345); // 使用try-with-resources语句,确保在try语句结束后自动关闭资源 try ( // 创建文件输出流,指定输出文件为"employee.ser" FileOutputStream fileOut = new FileOutputStream("employee.ser"); // 创建对象输出流,将对象写入到文件输出流中 ObjectOutputStream out = new ObjectOutputStream(fileOut) ) { // 将Employee对象写入输出流,即序列化对象 out.writeObject(emp); // 打印提示信息,表示序列化数据已保存到指定文件中 System.out.println("Serialized data is saved in employee.ser"); } catch (Exception e) { // 捕获并处理异常,打印堆栈跟踪信息 e.printStackTrace(); } } }
2. 反序列化示例
import java.io.FileInputStream; import java.io.ObjectInputStream; // 反序列化示例类 public class DeserializeExample { public static void main(String[] args) { Employee emp = null; // 声明一个Employee对象变量,并初始化为null,用于存储反序列化后的对象 try ( // 使用try-with-resources语句,确保在try语句结束后自动关闭资源 FileInputStream fileIn = new FileInputStream("employee.ser"); // 创建文件输入流,指定输入文件为"employee.ser" ObjectInputStream in = new ObjectInputStream(fileIn) // 创建对象输入流,从文件输入流中读取对象 ) { emp = (Employee) in.readObject(); // 从输入流中读取对象,即反序列化对象 } catch (Exception e) { // 捕获并处理异常,打印堆栈跟踪信息 e.printStackTrace(); } // 如果emp不为null,则表示反序列化成功 if (emp != null) { // 打印提示信息,表示反序列化成功 System.out.println("Deserialized Employee..."); // 打印反序列化后的员工姓名 System.out.println("Name: " + emp.name); // 打印反序列化后的员工ID System.out.println("ID: " + emp.id); } } }