1. 序列化和反序列化
-
序列化:
指把堆内存中的 Java 对象数据, 通过某种方式把对象存储到磁盘文件中
或者传递给其他网络的节点(在网络上传输)的这个过程称之为序列化. -
反序列化:
把磁盘文件中的对象数据或者把网络节点上的对象数据, 恢复成 Java 对象的过程. -
序列化的必要性:
-
在分布式系统中, 需要共享的数据的 JavaBean 对象, 都得做序列化.
若需要把对象在网络上传输, 就得把对象数据转换为二进制形式. -
以后存储在 HttpSession 中的对象, 都应该实现序列化接口
(只有实现序列化接口的类才能做序列化操作). -
服务器钝化:
- 如果服务器发现某些对象好久都没有活动了, 此时服务器就会把这些内存中的对象,
持久化在本地磁盘文件中(Java 对象 -> 二进制文件). - 如果某些对象需要活动的时候就先在内存中去寻找, 如果找到就使用,
找不到再去磁盘文件中, 反序列化我们的对象数据并恢复成 Java 对象.
- 如果服务器发现某些对象好久都没有活动了, 此时服务器就会把这些内存中的对象,
-
-
需要敞序列化的对象的类,必须实现序列化接口:
java.io.Serializable
接口.
(这个接口是标志接口, 并没有抽象方法需要进行实现).
底层会判断,如果当前对象是 Serializable 的实例, 才允许做序列化.
boolean ret = Java对象 instanceof Serializable;
-
在 Java 中大多数类都已经实现 Serializable 接口.
2. 对象流
-
通过使用对象流来完成序列化和反序列化的操作.
-
因为序列化和反序列化都是与二进制文件进行转换, 因此只需要字节流即可.
-
所以对象流分为两类, 输入流用作将二进制文件转换成 JAVA 对象, 输出流则相反
ObjectInputStream
对象输入流, 通过readObject
方法做反序列化操作ObjectOutputStream
对象输出流, 通过writeObject
方法做序列化操作
-
对象流的输入输出流实际上可以读取和写入多种数据类型的文件内容,
但这些方法用法相似, 因此只对readObject
和writeObject
两个方法进行示例.
2.1. 操作实例
- 先创建一个类来定义存储数据的对象.
//需要序列化的对象, 必须要实现序列化接口, 才能进行序列化操作
public class User implements java.io.Serializable{
private String name;
private String password;
private int age;
public User(String name, String pasaword, int age ){
this.name = name;
this.password = password;
this.age = age;
}
public String toString(){
return "User[ name=" + name + ", password=" + password + ", age=" + age + "]";
}
}
- 然后通过使用对象流来实现序列化和反序列化
public static void main(String[] args) thorows Exception{
File file = new File("file/obj.txt");
//进行序列化操作, 将java对象转化得出的二进制字节写入到文件中
writeObj(file);
//进行反序列化操作, 将二进制文件中的对象内容反序列化回java对象
User user = readObj(file);
//打印反序列化的出来java对象
System.out.println(user);
}
public static void writeObj(File file) thorows Exception{
//构建对象输出流, 需要同时包装一个文件输出流
ObjectOutputStream out = new ObjectOutputStream(new FileOutputStream(file));
//对一个对象进行序列化操作
out.writeObject(new User("Leo", "123", "18"));
//关闭资源
out.close();
}
public static User readObj(File file) thorows Exception{
//构建对象输入流, 同时需要包装一个文件输入流
ObjectInputStream in = new ObjectInputStream(new FileInputStream(file));
//对文件进行反序列化操作, 还原成JAVA对象, 因为返回类型是 Object 因此需要强转一下
User user = (User) in.readObject();
in.close();
return user;
}
2.2. 操作注意事项
-
对象中并非全部字段都需要进行序列化操作, 出于安全只对部分字段进行序列化
-
当有的字段, 例如密码, 不需要进行序列化操作的时候,
需要在其类中定义的成员变量前加上transient
修饰符进行修饰; -
表示这个字段是瞬态的, 并不需要序列化, 也不会被序列化.
例如上面的类中可以这样定义transient private String password;
-
-
序列化的版本问题
-
反序列化 Java 对象时必须提供该对象的 class 文件, 因为随着项目的升级,
系统的 class 文件可能也会升级(类中增加一个字段/删除一个字段); -
升级后的类对象可能多出或者减少字段, 若反序列化的文件是之前没升级时的类对象,
那么就会出现异常报错, 就会让反序列化失败, 因为转换出来的对象和现在的类不符合.
-
-
保证两个 class 文件的兼容性的解决方案
-
JAVA 中通过 serialVersionUID 序列化版本号来判断字节码是否发生变动,
如果不显式定义序列化版本号, 它的值由 JVM 根据类相关信息计算; -
而修改后的类的计算方式和之前不同. 就导致对象反序列化因版本不兼容而失败的问题.
-
因此需要在需要序列化的对象所属的类中, 定义一个版本号字段, 且这个字段是写死的.
通常可通过编译器生成, 如private static final long serialVersionUID = 1L;
-