深入理解序列化和反序列化
文章目录
1.是什么
序列化:就是讲对象转化成字节序列的过程。
反序列化:就是讲字节序列转化成对象的过程。
Java 提供了一种对象序列化的机制。用一个字节序列可以表示一个对象,该字节序列包含该对象的数据
、对象的类型
和对象中存储的属性
等信息。字节序列写出到文件之后,相当于文件中持久保存了一个对象的信息。
反之,该字节序列还可以从文件中读取回来,重构对象,对它进行反序列化。对象的数据
、对象的类型
和对象中存储的数据
信息,都可以用来在内存中创建对象。
2.为什么
那么为什么要去进行序列化操作呢?有两个原因:
- 持久化:对象是存储在 JVM 中的堆区的,但是如果 JVM 停止运行了,对象也就不存在了。序列化可以将对象转换成字节序列,可以写进硬盘中实现持久化。在新开期的 JVM 中可以读取字节序列进行反序列化成对象。
- 网络传输:网络直接传输数据,但是无法直接传输对象,可在传输前序列化,传输完成后反序列化对象。所以所有可以在网络中传输的对象都必须是可序列化的。也就是我们大家熟悉的
Json
3.怎么做
怎么去实现对象的序列化呢?
Java为我们提供了对象序列化的机制,规定了要实现序列化对象的类要满足的条件和实现方法。
- 对于要序列化对象的类必须实现
Serializable
接口或者Externalizable
接口 - 实现方法:JDK提供的
ObjectOutputStream
和ObjectInputStream
来实现序列化和反序列化
3.1 实现Serializable接口
public class Student implements Serializable {
private String name;
private Integer 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() {
return "Student{" +
"name='" + name + '\'' +
", age=" + age +
'}';
}
}
序列化
ObjectOutputStream类
java.io.ObjectOutputStream
类,将Java对象的原始数据类型写出到文件,实现对象的持久存储。
构造方法
public ObjectOutputStream(OutputStream out)
: 创建一个指定OutputStream的ObjectOutputStream。
构造举例,代码如下:
FileOutputStream fileOut = new FileOutputStream("stu.txt");
ObjectOutputStream out = new ObjectOutputStream(fileOut);
2.写出对象方法
public final void writeObject (Object obj)
: 将指定的对象写出。
public class SerializeDemo {
public static void main(String[] args) throws IOException {
FileOutputStream fos = new FileOutputStream("stu.txt");
ObjectOutputStream oos = new ObjectOutputStream(fos);
Student student = new Student();
student.setAge(18);
student.setName("xiaoxin");
oos.writeObject(student);
fos.close();
oos.close();
}
}
反序列化
ObjectInputStream类
ObjectInputStream反序列化流,将之前使用ObjectOutputStream序列化的原始数据恢复为对象。
构造方法
public ObjectInputStream(InputStream in)
: 创建一个指定InputStream的ObjectInputStream。
-
如果能找到一个对象的class文件,我们可以进行反序列化操作,调用
ObjectInputStream
读取对象的方法: -
public final Object readObject ()
: 读取一个对象。
public class DeserializationDemo {
public static void main(String[] args) throws Exception {
FileInputStream fis = new FileInputStream("stu.txt");
ObjectInputStream ois = new ObjectInputStream(fis);
Student student = (Student) ois.readObject();
fis.close();
ois.close();
System.out.println(student);// Student{name='xiaoxin', age=18}
}
}
3.2 实现Externalizable接口
实现Externalizable接口必须重写连个方法
- writeExternal(ObjectOutput out)
- readExternal(ObjectInput in)
如果序列化时一个字段没有序列化,那反序列化是要注意别给为序列化的字段反序列化了
举个栗子
public class Student implements Externalizable {
private String name;
private Integer 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() {
return "Student{" +
"name='" + name + '\'' +
", age=" + age +
'}';
}
@Override
public void writeExternal(ObjectOutput out) throws IOException {
out.writeInt(age);
out.writeObject(name);
}
@Override
public void readExternal(ObjectInput in) throws IOException, ClassNotFoundException {
this.age=in.readInt();
this.name= (String) in.readObject();
}
}
序列化
public static void main(String[] args) throws IOException {
FileOutputStream fos = new FileOutputStream("stu.txt");
ObjectOutputStream oos = new ObjectOutputStream(fos);
Student student = new Student();
student.setAge(18);
student.setName("xiaoxin");
oos.writeObject(student);
oos.close();
fos.close();
}
反序列化
public static void main(String[] args) throws Exception {
FileInputStream fis = new FileInputStream("stu.txt");
ObjectInputStream ois = new ObjectInputStream(fis);
Student student = (Student) ois.readObject();
System.out.println(student);
ois.close();
fis.close();
}
3.3 注意知识点
-
一个对象要进行序列化,如果该对象成员类型是引用类型的,那这个引用类型也一定要是可序列化的,否则会报错
-
同一个对象多次序列化成字节序列,这多个字节序列反序列化成的对象还是一个(使用==判断为true)(因为所有序列化保存的对象都会生成一个序列化编号,当再次序列化时会去检查此对象是否已经序列化了,如果是,那序列化只会输出上个序列化的编号)
-
如果序列化一个可变对象,序列化之后,修改对象属性值,再次序列化,只会保存上次序列化的编号(这是个坑注意下)
-
对于不想序列化的字段可以再字段类型之前加上transient关键字修饰(反序列化时会被赋予默认值)
-
另外,当JVM反序列化对象时,能找到class文件,但是class文件在序列化对象之后发生了修改,那么反序列化操作也会失败,抛出一个
InvalidClassException
异常。发生这个异常的原因如下:-
该类的序列版本号与从流中读取的类描述符的版本号不匹配
-
该类包含未知数据类型
-
该类没有可访问的无参数构造方法
-
3.4 serialVersionUID的作用
Serializable
接口给需要序列化的类,提供了一个序列版本号。serialVersionUID
该版本号的目的在于验证序列化的对象和对应类是否版本匹配。
先讲述下序列化的过程:在进行序列化时,会把当前类的serialVersionUID写入到字节序列中(也会写入序列化的文件中),在反序列化时会将字节流中的serialVersionUID同本地对象中的serialVersionUID进行对比,一致的话进行反序列化,不一致则失败报错(报InvalidCastException异常)
serialVersionUID
的生成有三种方式(private static final long serialVersionUID= XXXL
):
- 显式声明:默认的1L
- 显式声明:根据包名、类名、继承关系、非私有的方法和属性以及参数、返回值等诸多因素计算出的64位的hash值
- 隐式声明:未显式的声明serialVersionUID时java序列化机制会根据Class自动生成一个serialVersionUID(最好不要这样,因为如果Class发生变化,自动生成的serialVersionUID可能会随之发生变化,导致匹配不上)
序列化类增加属性时,最好不要修改serialVersionUID,避免反序列化失败
public class Employee implements java.io.Serializable {
// 加入序列版本号
private static final long serialVersionUID = 1L;
public String name;
public String address;
// 添加新的属性 ,重新编译, 可以反序列化,该属性赋为默认值.
public int eid;
public void addressCheck() {
System.out.println("Address check : " + name + " -- " + address);
}
}
4 扩展
其实对于对象转化成json字符串和json字符串转化成对象,也是属于序列化和反序列化的范畴,相对于JDK提供的序列化机制,各有各的优缺点:
- JDK序列化/反序列化:原生方法不依赖其他类库、但是不能跨平台使用、字节数较大
- json序列化/反序列化:json字符串可读性高、可跨平台使用无语言限制、扩展性好、但是需要第三方类库、字节数较大
如果文章对您有帮助,希望点个赞/收藏/关注! O(∩_∩)O~