Java–序列化与反序列化
应用场景
- 永久性保存对象,保存对象的字节序列到本地文件或者数据库中
- 通过序列化以字节流的形式使对象在网络中进行传递和接收
- 通过序列化在进程间传递对象
使用方法
我们在序列化的时候可以实现两个接口:Serializable、Externalizable(继承了前者)通过 ObjectInputStream、ObjectOutputStream 来发送对象或者读取对象。
我们可以保存在本地文件中、数据库(blob)、网络传输都可以用序列化来把Java对象变成字节序列传输保存对象。
首先来个简单的序列化例子:
1 2 3 4 5 6 7 8 9 10 11 12 13
| public static void main(String[] args) throws IOException, ClassNotFoundException {
String file = "D:\\Git\\data\\IO操作\\ddd.txt"; @Cleanup ObjectOutputStream oos = new ObjectOutputStream(new FileOutputStream(file)); Student student = new Student(1,"张三",20,"男"); oos.writeObject(student); oos.flush();
@Cleanup ObjectInputStream ois = new ObjectInputStream(new FileInputStream(file)); Student stu = (Student)ois.readObject(); System.out.println(stu); }
|
Student类:
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 26
| @Data public class Student implements Serializable { public static final long serialVersionUID = 1L; private transient int stu_id; private String stuName; private int stuAge; private String stuSex; public Student(int stu_id, String stuName, int stuAge, String stuSex) { this.stu_id = stu_id; this.stuName = stuName; this.stuAge = stuAge; this.stuSex = stuSex; } private void writeObject(java.io.ObjectOutputStream s) throws java.io.IOException{ s.defaultWriteObject(); s.writeInt(stu_id); } private void readObject(java.io.ObjectInputStream s) throws java.io.IOException,ClassNotFoundException{ s.defaultReadObject(); this.stu_id=s.readInt(); }
}
|
执行结果:
1
| Student(stu_id=1, stuName=张三, stuAge=20, stuSex=男)
|
Serializable、Externalizable 的比较
Serializable 是标识接口
Serializable提供了两种方式进行对象的序列化
- 采用默认序列化方式,将非transatient和非static的属性进行序列化
- 编写readObject和writeObject完成部分属性的序列化
Externalizable 接口的序列化,需要重写writeExternal和readExternal方法,并且在方法中编写相关的逻辑完成序列化和反序列化。
Externalizable接口的实现方式一定要有默认的无参构造函数,Serializable接口实现,其采用反射机制完成内容恢复,没有一定要有无参构造函数的限制
采用Externalizable无需产生序列化ID(serialVersionUID)而Serializable接口则需要
相比较Serializable, Externalizable序列化、反序列更加快速,占用相比较小的内存
在项目中,大部分的类还是推荐使用Serializable, 有些类可以使用Externalizable接口,如:
- 完全控制序列的流程和逻辑
- 需要大量的序列化和反序列化操作,而你比较关注资源和性能~ 当然,这种情况下,我们一般还会考虑第三方序列化/反序列化工具,如protobuf等进行序列化和反序列化操作
可能出现的问题
当我们不想实现序列化某个属性时,可以通过 transient
、重写 writeObject
,readObject
、使用Externalizable
实现,这三种方式来实现属性的部分序列化。
其目的是序列化对象版本控制,如果发送方和接收方都有一个相同的对象类型都实现了序列化接口,但是他们的serialVersionUID
不一致,这样就会导致反序列化失败。
静态变量是不参与序列化过程的
在一个对象进行序列化时,如果属性有其他对象也会一并的进行序列化操作,但是要保证属性对象也实现了Serializable
接口,否则使用时会报错
也就是说我想序列化我却不能给里面的属性对象也加上Serializable
接口,那么我们可以尝试继承该类,然后让它实现序列化接口,但是下面两种情况不能序列化,那么这时我们只能用transient
来修饰这个对象了
- 如果引用类被定义为final
- 如果引用类引用了另外一个非可序列化的对象
需要牢记的,ObjectInputStream
读取数据的顺序和ObjectOutputStream
写入数据的顺序是一致的.
我们分为两点考虑,一个是父类可以序列化一个是不可序列化
- 父类可以序列化时,那么子类继承父类也是可以进行序列化的
- 父类不可以序列化时,当子类进行反序列化时所有继承于父类的实例变量值,都会通过调用非序列化构造函数来初始化,也就是说我们不能序列化父类的属性。
Java 序列化机制为了节省磁盘空间,具有特定的存储规则,当写入文件的为同一对象时,并不会再将对象的内容进行存储,而只是再次存储一份引用
单例模式下的坑
这部分单独拿出来说,拿双重检查锁模式来说
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22
| public class Singleton implements Serializable {
public static final Long serialVersionUID = 1L;
private static volatile Singleton singleton;
private Singleton(){
}
public static Singleton getInstance(){ if (singleton == null){ synchronized (Singleton.class){ if (singleton == null){ singleton = new Singleton(); } } } return singleton; } }
|
测试类:
1 2 3 4 5 6 7 8 9 10 11 12 13 14
| public static void main(String[] args) throws IOException, ClassNotFoundException { Singleton singleton = Singleton.getInstance();
String file = "D:\\Git\\data\\IO操作\\ddd.txt"; @Cleanup ObjectOutputStream oos = new ObjectOutputStream(new FileOutputStream(file)); System.out.println(singleton); oos.writeObject(singleton); oos.flush();
@Cleanup ObjectInputStream ois = new ObjectInputStream(new FileInputStream(file)); singleton = (Singleton)ois.readObject(); System.out.println(singleton);
}
|
测试结果:
1 2
| com.example.demo.SingletonDemo.Singleton@685f4c2e com.example.demo.SingletonDemo.Singleton@6aa8ceb6
|
哦豁该怎么办,Java已经考虑好这种情况readResolve
,接下来修改代码:
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 26
| public class Singleton implements Serializable {
public static final Long serialVersionUID = 1L;
private static volatile Singleton singleton2;
private Singleton(){
}
public static Singleton getInstance(){ if (singleton == null){ synchronized (Singleton.class){ if (singleton == null){ singleton = new Singleton(); } } } return singleton; } private Object readResolve(){ System.out.println("read Resolve"); return singleton; } }
|
再次测试结果:
1 2 3
| com.example.demo.SingletonDemo.Singleton@685f4c2e read Resolve com.example.demo.SingletonDemo.Singleton@685f4c2e
|