序列化与反序列化
一、目的
为了方便对象IO传输
1.1 作用
当两个进程在远程通信时候,可以发送各种类型的数据,包括jpg、mp4、mp3等媒体文件,这些数据会以二进制序列的形式进行传送。当两个java进程在通信时候,java需要序列化和反序列化进行进程间的对象传送。
例如发送方需要将java对象序列化成二进制字节序列,IO传输;接收方通过二进制字节序列读取出发送方传递的java对象。
二、序列化:将 Java 对象转换成字节流的过程
2.1 序列化过程:
是指把一个 Java 对象变成二进制内容,实质上就是一个 byte[]。
因为序列化后可以把 byte[] 保存到文件中,
或者把 byte[] 通过网络传输到远程(IO)
,如此就相当于把 Java 对象存储到文件或者通过网络传输出去了。
2.2 重点
Java 对象要能序列化,必须实现一个特殊的java.io.Serializable接口,
接口是一个标识空接口,没有定义任何方法,就是为了标识对象可以序列化。
如果要序列化的某些对象未实现java.io.Serializable
接口,则此方法将引发NotSerializableException
。
三、反序列化:将字节流转换成 Java 对象
3.1 反序列化过程:
把一个二进制内容(也就是 byte[] 数组)变回 Java 对象。
通过反序列化,保存到文件中的 byte[] 数组又可以“变回” Java 对象,
或者从网络上读取 byte[] 并把它“变回” Java 对象。
3.2 作用
- 以对象形式将数据存储到磁盘上,例如Redis存储对象列表。
- 将程序状态保存在磁盘上,例如将巫师三存档。
- 通过网络以表单对象发送数据,例如在聊天app以对象形式发送信息。
四、序列化工作原理
上文说到,我们只需要将对象实现序列化标识接口,
该对象就有资格进行序列化。
真正的序列化对象,jvm帮我们进行该类的对象写入持久性存储
或通过网络进行读取。
当然我们也可以通过ObjectInputStream
和ObjectOutputStream
对象IO流类实现序列化和反序列化操作。
五、serialVersionUID常数
serialVersionUID 是一个常数,用于唯一标识可序列化类的版本。
从输入流构造对象时,JVM 在反序列化过程中检查此常数。如
果正在读取的对象的 serialVersionUID 与类中指定的序列号不同,
则 JVM 抛出InvalidClassException。
这是为了确保正在构造的对象与具有相同 serialVersionUID 的类兼容。
serialVersionUID 是可选的。如果不显式声明,Java 编译器将自动生成一个。
为什么要显示声明serialVersionUID
?
因为java编译器会对要序列化的java对象跟据器类的元素计算UID,有一个改变,
serialVersionUID会随之改变。那如果,一个程序将某个对象序列化到文件中,
没有为其声明显式UID,那更新类后,比如添加个有参构造函数,
那自动生成的UID都变了,那先前序列化的java对象UID都不同,就没法反序列化成功了。
六、瞬时变量 transient
如果成员变量不需要序列化,则可以将其标记为瞬态。这意味着在序列化对象时不会存储 瞬时变量的值。 ## 七、例子 Student类
package day627.serial;
import java.io.Serializable;
public class Student implements Serializable {
//显式UID
private static final long serialVersionUID = 1014100089306623762L;
// 姓名
private String name;
// 年龄
//将年龄设置为瞬时变量
private transient Integer age;
public Student() {
}
public Student(String name, Integer age) {
this.name = name;
this.age = age;
}
public String getName() {
return name;
}
public void setName(String name) {
this.name = name;
}
public Integer getAge() {
return age;
}
public void setAge(Integer age) {
this.age = age;
}
@Override
public String toString() {
// 优化toString方法
StringBuilder sb = new StringBuilder();
sb.append("[").append("name = ").append(this.name).append(", ").append("age =").append(this.age).append("]");
return sb.toString();
}
}
测试类
package day627.serial;
import java.io.FileInputStream;
import java.io.FileOutputStream;
import java.io.ObjectInputStream;
import java.io.ObjectOutputStream;
import java.util.ArrayList;
public class Test01 {
public static void main(String[] args) throws Exception {
Student s = new Student();
System.out.println(s);
//创建对象操作流 --> 序列化
ObjectOutputStream oos = new ObjectOutputStream(new FileOutputStream("C:\\lipp\\company\\obj.txt"));
//创建集合,且添加学生对象
ArrayList<Student> list = new ArrayList<Student>();
list.add(new Student("悔创阿里杰克马", 51));
list.add(new Student("会点一点长几颗", 26));
list.add(new Student("容颜老去蒋青青", 32));
list.add(new Student("将里最丑刘一飞", 27));
//将集合写入到文件
oos.writeObject(list);
//创建对象输入流 --> 反序列化
ObjectInputStream ois = new ObjectInputStream(new FileInputStream("C:\\lipp\\company\\obj.txt"));
//读取数据
Object o = ois.readObject();
//向下转型
ArrayList<Student> al = (ArrayList<Student>) o;
//遍历集合
for (int i = 0; i < al.size(); i++) {
//根据索引取出集合的每一个元素
Student stu = al.get(i);
System.out.println(stu);
}
}
}
result
[name = null, age =null]
[name = 悔创阿里杰克马, age =null]
[name = 会点一点长几颗, age =null]
[name = 容颜老去蒋青青, age =null]
[name = 将里最丑刘一飞, age =null]