java序列化与反序列化全讲解
⽬录
1 概述
序列化与反序列化
Java序列化是指把Java对象转换为字节序列的过程;⽽Java反序列化是指把字节序列恢复为Java对象的过程。
序列化分为两⼤部分:序列化和反序列化。序列化是这个过程的第⼀部分,将数据分解成字节流,以便存储在⽂件中或在⽹络上传输。反序列化就是打开字节流并重构对象。对象序列化不仅要将基本数据类型转换成字节表⽰,有时还要恢复数据。恢复数据要求有恢复数据的对象实例。
为什么需要序列化与反序列化
我们知道,当两个进程进⾏远程通信时,可以相互发送各种类型的数据,包括⽂本、图⽚、⾳频、视频等, ⽽这些数据都会以⼆进制序列的形式在⽹络上传送。那么当两个Java进程进⾏通信时,能否实现进程间的对象传送呢?答案是可以的。如何做到呢?这就需要Java序列化与反序列化了。换句话说,⼀⽅
⾯,发送⽅需要把这个Java对象转换为字节序列,然后在⽹络上传送;另⼀⽅⾯,接收⽅需要从字节序列中恢复出Java对象。
当我们明晰了为什么需要Java序列化和反序列化后,我们很⾃然地会想Java序列化的好处。其好处⼀是实现了数据的持久化,通过序列化可以把数据永久地保存到硬盘上(通常存放在⽂件⾥),⼆是,利⽤序列化实现远程通信,即在⽹络上传送对象的字节序列。
① 想把内存中的对象保存到⼀个⽂件中或者数据库中时候;
② 想⽤套接字在⽹络上传送对象的时候;
③ 想通过RMI传输对象的时候
⼀些应⽤场景,涉及到将对象转化成⼆进制,序列化保证了能够成功读取到保存的对象。
⼏种常见的序列化和反序列化协议
XML&SOAP
XML 是⼀种常⽤的序列化和反序列化协议,具有跨机器,跨语⾔等优点,SOAP(Simple Object Access protocol) 是⼀种被⼴泛应⽤的,基于 XML 为序列化和反序列化协议的结构化消息传递协议
JSON(Javascript Object Notation)
Protobuf
2 序列化实现
只有实现了Serializable或者Externalizable接⼝的类的对象才能被序列化为字节序列。(不是则会抛出异常)
Serializable 接⼝
是 Java 提供的序列化接⼝,它是⼀个空接⼝
public interface Serializable {
}
Serializable ⽤来标识当前类可以被 ObjectOutputStream 序列化,以及被 ObjectInputStream 反序列化。
Serializable 接⼝的基本使⽤
通过 ObjectOutputStream 将需要序列化数据写⼊到流中,因为 Java IO 是⼀种装饰者模式,因此可以通过 ObjectOutStream 包装FileOutStream 将数据写⼊到⽂件中或者包装 ByteArrayOutStream 将数据写⼊到内存中。同理,可以通过 ObjectInputStream 将数据从磁盘 FileInputStream 或者内存 ByteArrayInputStream 读取出来然后转化为指定的对象即可。
Serializable 接⼝的特点
1. 序列化类的属性没有实现 Serializable 那么在序列化就会报错
具体可以跟进 ObjectOutputStream#writeObject() 源码查看具体原因:
Exception in thread "main" java.io.NotSerializableException: ample.seriable.Color
public class Student implements Serializable {
private String name;
private int age;
/**
* Color 类也是需要实现序列化接⼝的。
*/
private Color color;//这⾥如果没有实现序列化接⼝,那么在 Student 对象序列化时将会报错
}
2. 在反序列化过程中,它的⽗类如果没有实现序列化接⼝,那么将需要提供⽆参构造函数来重新创建对象。Animal 是⽗类,它没有实现 Serilizable 接⼝
public class Animal {
private String color;
public Animal() {//没有⽆参构造将会报错
System.out.println("调⽤ Animal ⽆参构造");
}
public Animal(String color) {
System.out.println("调⽤ Animal 有 color 参数的构造");
}
@Override
public String toString() {
return "Animal{" +
"color='" + color + '\'' +
'}';
}
}
BlackCat 是 Animal 的⼦类
public class BlackCat extends Animal implements Serializable {
private static final long serialVersionUID = 1L;
private String name;
public BlackCat() {
super();
System.out.println("调⽤⿊猫的⽆参构造");
}
public BlackCat(String color, String name) {
super(color);
this.name = name;
System.out.println("调⽤⿊猫有 color 参数的构造");
}
@Override
public String toString() {
return "BlackCat{" +
"name='" + name + '\'' +String() +'\'' +
'}';
}
}
SuperMain 测试类
public class SuperMain {
private static final String FILE_PATH = "./super.bin";
public static void main(String[] args) throws Exception {
serializeAnimal();
deserializeAnimal();
}
private static void serializeAnimal() throws Exception {
BlackCat black = new BlackCat("black", "我是⿊猫");
System.out.println("序列化前:"+String());
System.out.println("=================开始序列化================");
ObjectOutputStream oos = new ObjectOutputStream(new FileOutputStream(FILE_PATH)); oos.writeObject(black);
oos.flush();
oos.close();
}
private static void deserializeAnimal() throws Exception {
System.out.println("=================开始反序列化================");
ObjectInputStream ois = new ObjectInputStream(new FileInputStream(FILE_PATH));
BlackCat black = (BlackCat) adObject();
ois.close();
System.out.println(black);
}
}
输出结果
调⽤ Animal 有 color 参数的构造
调⽤⿊猫有 color 参数的构造
序列化前:BlackCat{name='我是⿊猫'Animal{color='black'}'}
=================开始序列化================
=================开始反序列化================
调⽤ Animal ⽆参构造
BlackCat{name='我是⿊猫'Animal{color='null'}'}
从上⾯的执⾏结果来看,如果要序列化的对象的⽗类 Animal 没有实现序列化接⼝,那么在反序列化时是会调⽤对应的⽆参构造⽅法的,这样做的⽬的是重新初始化⽗类的属性,例如 Animal 因为没有实现序列化接⼝,因此对应的 color 属性就不会被序列化,因此反序列得到的color 值就为 null。
对上⾯的2个操作⽂件流的类的简单说明
ObjectOutputStream代表对象输出流:
它的writeObject(Object obj)⽅法可对参数指定的obj对象进⾏序列化,把得到的字节序列写到⼀个⽬标输出流中。
ObjectInputStream代表对象输⼊流:java接口有没有构造方法
它的readObject()⽅法从⼀个源输⼊流中读取字节序列,再把它们反序列化为⼀个对象,并将其返回。
3. ⼀个实现 Serializable 接⼝的⼦类也是可以被序列化的。
4. 静态成员变量是不能被序列化
序列化是针对对象属性的,⽽静态成员变量是属于类的。
5. transient 标识的对象成员变量不参与序列化
在下⾯这个栗⼦中,MyList 这个类定义了⼀个 arr 数组属性,初始化的数组长度为 100。在实际序列化时如果让 arr 属性参与序列化的话,那么长度为 100 的数组都会被序列化下来,但是我在数组中可能只存放 30 个数组⽽已,这明显是不可理的,所以这⾥就要⾃定义序列化过程啦,具体的做法是写以下两个 private ⽅法:
private void writeObject(java.io.ObjectOutputStream s)throws java.io.IOException
private void readObject(java.io.ObjectInputStream s)throws java.io.IOException, ClassNotFoundException
从这两个⽅法的名字就可以看出分别是序列化写⼊数据和反序列化读取数据⽤的,那么这两个⽅法是在哪⾥使⽤呢?其实在序列化和反序列化过程中会通过反射调⽤的,具体下⾯会分析这个过程哦。
现在来看看这个 transient 应⽤:
public class MyList implements Serializable {
private String name;
/*
transient 表⽰该成员 arr 不需要被序列化
*/
private transient Object[] arr;
public MyList() {
}
public MyList(String name) {
this.name = name;
this.arr = new Object[100];
/
*
给前⾯30个元素进⾏初始化
版权声明:本站内容均来自互联网,仅供演示用,请勿用于商业和其他非法用途。如果侵犯了您的权益请与我们联系QQ:729038198,我们将在24小时内删除。
发表评论