目录
  1. 1. Java–序列化与反序列化
    1. 1.1. 应用场景
    2. 1.2. 使用方法
    3. 1.3. Serializable、Externalizable 的比较
    4. 1.4. 可能出现的问题
    5. 1.5. 单例模式下的坑
Java--序列化与反序列化

Java–序列化与反序列化

应用场景

  1. 永久性保存对象,保存对象的字节序列到本地文件或者数据库中
  2. 通过序列化以字节流的形式使对象在网络中进行传递和接收
  3. 通过序列化在进程间传递对象

使用方法

  我们在序列化的时候可以实现两个接口: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";
//这个注解是lombok提供的用来关闭流的,我加上了总感觉没自己关闭舒服 :)
@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 {
//这个是序列化ID,一般常用默认为 1L 就行,也可以是对象生产的哈希码
public static final long serialVersionUID = 1L;
//transient不会序列化
private transient int stu_id; //该元素不会进行 jvm 默认的序列化,但是也可以自己完成序列化
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;
}
//用transient修饰之后,就可以通过重写这两个方法来完成定制的序列化操作
private void writeObject(java.io.ObjectOutputStream s) throws java.io.IOException{
s.defaultWriteObject();//执行 jvm 默认序列化的元素进行序列化操作
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 的比较

  1. Serializable 是标识接口

  2. Serializable提供了两种方式进行对象的序列化

    1. 采用默认序列化方式,将非transatient和非static的属性进行序列化
    2. 编写readObject和writeObject完成部分属性的序列化

    Externalizable 接口的序列化,需要重写writeExternal和readExternal方法,并且在方法中编写相关的逻辑完成序列化和反序列化。

  3. Externalizable接口的实现方式一定要有默认的无参构造函数,Serializable接口实现,其采用反射机制完成内容恢复,没有一定要有无参构造函数的限制

  4. 采用Externalizable无需产生序列化ID(serialVersionUID)而Serializable接口则需要

  5. 相比较Serializable, Externalizable序列化、反序列更加快速,占用相比较小的内存

在项目中,大部分的类还是推荐使用Serializable, 有些类可以使用Externalizable接口,如:

  • 完全控制序列的流程和逻辑
  • 需要大量的序列化和反序列化操作,而你比较关注资源和性能~ 当然,这种情况下,我们一般还会考虑第三方序列化/反序列化工具,如protobuf等进行序列化和反序列化操作

可能出现的问题

  • 序列化部分属性

  当我们不想实现序列化某个属性时,可以通过 transient、重写 writeObjectreadObject、使用Externalizable实现,这三种方式来实现属性的部分序列化。

  • serialVersionUID不一致

  其目的是序列化对象版本控制,如果发送方和接收方都有一个相同的对象类型都实现了序列化接口,但是他们的serialVersionUID不一致,这样就会导致反序列化失败。

  • 静态变量的序列化

  静态变量是不参与序列化过程的

  • 对象里的属性是另外一个对象

  在一个对象进行序列化时,如果属性有其他对象也会一并的进行序列化操作,但是要保证属性对象也实现了Serializable接口,否则使用时会报错

  • 无法访问引用对象的源代码

  也就是说我想序列化我却不能给里面的属性对象也加上Serializable接口,那么我们可以尝试继承该类,然后让它实现序列化接口,但是下面两种情况不能序列化,那么这时我们只能用transient来修饰这个对象了

  1. 如果引用类被定义为final
  2. 如果引用类引用了另外一个非可序列化的对象
  • 读写顺序一致

  需要牢记的,ObjectInputStream读取数据的顺序和ObjectOutputStream写入数据的顺序是一致的.

  • 继承对于序列化的影响

  我们分为两点考虑,一个是父类可以序列化一个是不可序列化

  1. 父类可以序列化时,那么子类继承父类也是可以进行序列化的
  2. 父类不可以序列化时,当子类进行反序列化时所有继承于父类的实例变量值,都会通过调用非序列化构造函数来初始化,也就是说我们不能序列化父类的属性。
  • 序列化机制的特殊规则

  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;
}
//加入 readResolve 方法,直接返回对象实例
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
文章作者: Archiver
文章链接: https://www.kaiming66.com/2020/02/13/Java/Java--%E5%BA%8F%E5%88%97%E5%8C%96%E4%B8%8E%E5%8F%8D%E5%BA%8F%E5%88%97%E5%8C%96/
版权声明: 本博客所有文章除特别声明外,均采用 CC BY-NC-SA 4.0 许可协议。转载请注明来自 Archiver`s Blog
打赏
  • 微信
  • 支付寶

评论