一、概述
- 序列化
- 将程序中的对象直接以文件的形式存储,是按照对象在内存中的序列进行存储的
- 反序列化
- 从文件中把对象数据重新读到程序中
- 就是把之前序列化的对象从文件里读出,重新变成对象
- 从文件中把对象数据重新读到程序中
- 2018年java官方发现程序中至少有1/3的bug是因为使用了序列化和反序列化技术产生的,这种序列化安全问题如果不能有更合适的技术替代,短期内是不会剔除序列化技术的,否则会影响整个java生态
二、实现序列化
1、Serializable接口实现序列化
- 想要实现序列化的类,需要实现Serializable接口,使用到对象输出流ObjectOutputStream
- 这是一个标记接口
- 底层源码中,ObjectOutputStream会判断写入的对象是否为字符串,或者使用instanceof判断是否是Serializable或其实现类,如果不满足这些条件会抛出异常
- 使用ObjectOutputStream需要传入一个OutputStream对象
- 实现Serializable接口的类如果想要序列化,所有属性也必须实现Serializable接口,否则无法序列化
2、实现部分属性序列化的四种方式
- 使用transient修饰属性
- 前提是要该类实现Serializable接口
- 被transient修饰的属性不会被序列化处理
- 使用static修饰符
- 前提是要该类实现Serializable接口
- 被static修饰的属性不会被序列化处理
- 使用默认方法writeObject和readObject
- 在实现Serializable接口的类中定义这两个方法
- 这两个方法必须都是private void修饰,否则不生效
- writeObject方法用于自定义序列化的内容和顺序
- readObject方法用于自定义反序列化的内容和顺序,需要和writeObject方法的内容顺序一致
- 实现Externalizable接口
- Externalizable接口继承自Serializable接口
- 需要重写两个方法
- writeExternal
- 入参ObjectOutput out
- 使用out.writeObject(属性名)指定需要序列化的属性
- readExternal
- 入参ObjectInput in
- 使用in.readObject()获取反序列化后的属性值,需要与序列化属性的顺序一致
- writeExternal
3、举例
a、验证transient和static修饰的属性无法被序列化
验证transient和static修饰的属性无法被序列化
- 定义Person类,实现Serializable接口。其中
- a属性使用transient修饰(无法被序列化),b属性使用static修饰(无法被序列化),c属性正常定义(可以被序列化)
package SerializableTest;
import java.io.Serializable;
public class Person implements Serializable {
//使用transient修饰无法被序列化
private transient String a;
private static String b;
private String c;
@Override
public String toString() {
return "Person{" +
"a='" + a + '\'' +
", b='" + b + '\'' +
", c='" + c + '\'' +
'}';
}
public String getA() {
return a;
}
public void setA(String a) {
this.a = a;
}
public static String getB() {
return b;
}
public static void setB(String b) {
Person.b = b;
}
public String getC() {
return c;
}
public void setC(String c) {
this.c = c;
}
}
- 定义序列化工具类
package SerializableTest;
import java.io.*;
public class SerializableUtils {
//序列化操作
public static void mySerializable(Object obj,String fileName) throws IOException {
FileOutputStream fos = new FileOutputStream(fileName);
ObjectOutputStream oos = new ObjectOutputStream(fos);
oos.writeObject(obj);
oos.close();
fos.close();
}
//反序列化操作
public static Object myDesSerializable(String fileName) throws IOException, ClassNotFoundException {
FileInputStream fis = new FileInputStream(fileName);
ObjectInputStream ois = new ObjectInputStream(fis);
Object o = ois.readObject();
return o;
}
}
- 定义测试类
package SerializableTest;
import java.io.IOException;
public class test {
public static void main(String[] args) throws IOException, ClassNotFoundException {
Person p = new Person();
p.setA("A");
Person.setB("B");
p.setC("C");
System.out.println("序列化之前的p对象:"+p); //Person{a='A', b='B', c='C'}
//序列化操作
SerializableUtils.mySerializable(p,"I:\\a.txt");
//修改b属性的值,不会影响到a.txt文件中的b属性值
Person.setB("bbbb");
//反序列化操作
Object o = SerializableUtils.myDesSerializable("I:\\a.txt");
if (o instanceof Person){
p = (Person) o;
}
//b是static修饰的,如果也能序列化,结果应该还是B,但是这里的结果是bbb,说明static修饰不可被序列化
//a为null,说明在序列化操作的时候a属性并没有被序列化
System.out.println("序列化后的p对象:"+p); //Person{a='null', b='bbbb', c='C'}
}
}
- 经过测试发现,transient和static修饰的属性确实无法被实例化
b、验证使用默认方法writeObject和readObject指定序列化的属性
验证使用默认方法writeObject和readObject指定序列化的属性
- 实现Serializable接口默认会
- 重新编写Person类,令a和b进行序列化,c不进行序列化
package SerializableTest;
import java.io.IOException;
import java.io.Serializable;
public class Person implements Serializable {
//使用transient修饰无法被序列化
private String a;
private String b;
private String c;
private void writeObject(java.io.ObjectOutputStream out) throws IOException {
//序列化a和b属性,不序列化c属性
System.out.println("writeObject进行序列化处理...");
out.writeObject(a);
out.writeObject(b);
}
private void readObject(java.io.ObjectInputStream in) throws IOException, ClassNotFoundException {
//反序列化读取a和b属性的值
System.out.println("readObject进行反序列化处理...");
a = (String) in.readObject();
b = (String) in.readObject();
}
@Override
public String toString() {
return "Person{" +
"a='" + a + '\'' +
", b='" + b + '\'' +
", c='" + c + '\'' +
'}';
}
public String getA() {
return a;
}
public void setA(String a) {
this.a = a;
}
public String getB() {
return b;
}
public void setB(String b) {
this.b = b;
}
public String getC() {
return c;
}
public void setC(String c) {
this.c = c;
}
}
- 工具类不变
- 测试类
package SerializableTest;
import java.io.IOException;
public class test {
public static void main(String[] args) throws IOException, ClassNotFoundException {
Person p = new Person();
p.setA("A");
p.setB("B");
p.setC("C");
System.out.println("序列化之前的p对象:"+p); //Person{a='A', b='B', c='C'}
//序列化操作
SerializableUtils.mySerializable(p,"I:\\a.txt");
//修改b属性的值,不会影响到a.txt文件中的b属性值
p.setB("bbbb");
//反序列化操作
Object o = SerializableUtils.myDesSerializable("I:\\a.txt");
if (o instanceof Person){
p = (Person) o;
}
//b是static修饰的,如果也能序列化,结果应该还是B,但是这里的结果是bbb,说明static修饰不可被序列化
//a为null,说明在序列化操作的时候a属性并没有被序列化
System.out.println("序列化后的p对象:"+p); //Person{a='null', b='bbbb', c='C'}
}
}
- 测试结果
- 可见c确实不能被序列化
c、验证实现Externalizable接口序列化部分属性
验证实现Externalizable接口序列化部分属性
- 修改Person类,实现Externalizable接口,并重写两个方法
package SerializableTest;
import java.io.Externalizable;
import java.io.IOException;
import java.io.ObjectInput;
import java.io.ObjectOutput;
public class Person implements Externalizable {
//使用transient修饰无法被序列化
private String a;
private String b;
private String c;
@Override
public void writeExternal(ObjectOutput out) throws IOException {
System.out.println("writeExternal进行序列化操作...");
out.writeObject(a);
out.writeObject(b);
}
@Override
public void readExternal(ObjectInput in) throws IOException, ClassNotFoundException {
System.out.println("readExternal进行反序列化操作...");
a = (String) in.readObject();
b = (String) in.readObject();
}
@Override
public String toString() {
return "Person{" +
"a='" + a + '\'' +
", b='" + b + '\'' +
", c='" + c + '\'' +
'}';
}
public String getA() {
return a;
}
public void setA(String a) {
this.a = a;
}
public String getB() {
return b;
}
public void setB(String b) {
this.b = b;
}
public String getC() {
return c;
}
public void setC(String c) {
this.c = c;
}
}
- 工具类保持不变
- 测试类
package SerializableTest;
import java.io.IOException;
public class test {
public static void main(String[] args) throws IOException, ClassNotFoundException {
Person p = new Person();
p.setA("A");
p.setB("B");
p.setC("C");
System.out.println("序列化之前的p对象:"+p); //Person{a='A', b='B', c='C'}
//序列化操作
SerializableUtils.mySerializable(p,"I:\\a.txt");
//修改b属性的值,不会影响到a.txt文件中的b属性值
p.setB("bbbb");
//反序列化操作
Object o = SerializableUtils.myDesSerializable("I:\\a.txt");
if (o instanceof Person){
p = (Person) o;
}
//b是static修饰的,如果也能序列化,结果应该还是B,但是这里的结果是bbb,说明static修饰不可被序列化
//a为null,说明在序列化操作的时候a属性并没有被序列化
System.out.println("序列化后的p对象:"+p); //Person{a='null', b='bbbb', c='C'}
}
}
- 测试结果
- c未被序列化,说明实现Externalizable接口也能实现序列化部分属性
d、Serializable接口与Externalizable接口的区别
区别 | Serializable | Externalizable |
实现复杂度 | 实现简单,java对其有内建支持 | 实现复杂,由开发人员自己完成 |
执行效率 | 所有属性由java统一保存,性能较低 | 开发人员决定对象保存哪些属性,可以提高执行效率 |
占用空间 | 保存数据占用空间大 | 部分存储,占用空间相对较小 |
使用频率 | 高 | 偏低 |
三、实现反序列化
- 将之前序列化存储的对象从文件中读出,重新变成程序中的对象
- 使用了ObjectInputStream
- 需要传入一个InputStream对象
- 举例见实现序列化的例子