Java序列化和反序列化

0x01 序列化和反序列化概念

序列化就是把对象转换成字节流,便于保存在内存、文件、数据库中;反序列化即逆过程,把字节流还原成对象。

0x02 为什么要引入序列化

究竟为什么要用序列化,不用不行吗?

image-20200604162949988

先看下序列化用途:

(1)永久性保存对象,保存对象的字节序列到本地文件或者数据库中

(2)通过序列化以字节流的形式使对象在网络中进行传递和接收

(3)通过序列化在进程间传递对象

网上看到基本就是这三个,如上图,基本就是在文件、网络、进程中传输字节流。看到这里还是不知道啊,往下看,网上找的栗子看了就懂了。

第一个例子:

Web 服务器中的 Session 会话对象,当有10万用户并发访问,就有可能出现10万个 Session 对象,显然这种情况内存可能是吃不消的。于是 Web 容器就会把一些 Session 先序列化,让他们离开内存空间,序列化到硬盘中,当需要调用时,再把保存在硬盘中的对象还原到内存中。

第二个例子:

当两个进程进行远程通信时,文本、图片、音频、视频的传输可以发送各种类型的数据,包括等, 而这些数据都会以二进制序列的形式在网络上传送。

到这里基本了解了序列化和反序列化,那具体java是怎么实现序列化的。

0x03 Java序列化

在Java中,主要有两个接口ObjectOutputStream(对象输出流) 和ObjectInputStream(对象输入流),ObjectOutputStream.writeObject()实现序列化,而ObjectInputStream.readObject()方法实现反序列化。

序列化 反序列化例子1:

执行命令的类CommandExec.java:

CommandExec.java
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
import java.io.*;

public class CommandExec implements Serializable{

public CommandExec() {
String cmd = "calc";

try {
Process process = Runtime.getRuntime().exec(cmd);
InputStream is = process.getInputStream();
InputStreamReader isr = new InputStreamReader(is);
BufferedReader br = new BufferedReader(isr);

for(String content = br.readLine(); content != null; content = br.readLine()) {
System.out.println(content);
}
} catch (IOException var7) {
var7.printStackTrace();
}

}
}

main.java

main.java
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
27
28
29
30
31
import java.io.*;

/**
* 序列化测试
*/
public class main {
public static void main(String[] args) throws Exception {
serializeCommandExec();
CommandExec commandExec = (CommandExec) deserializeCommandExec();

}

private static void serializeCommandExec() throws IOException {
CommandExec commandExec = new CommandExec();
// ObjectOutputStream 对象输出流
ObjectOutputStream oos = new ObjectOutputStream(new FileOutputStream(new File("d:/test.txt")));
oos.writeObject(commandExec);
System.out.println("对象序列化成功!");
}

/**
* 反序列化
*/

private static CommandExec deserializeCommandExec() throws Exception {
ObjectInputStream ois = new ObjectInputStream(new FileInputStream(new File("d:/test.txt")));
CommandExec commandExec = (CommandExec) ois.readObject();
System.out.println("对象反序列化成功!");
return commandExec;
}
}

以上代码的运行结果如下图,在构造对象时弹出1个计算器,序列化生成test.txt文件,并且由文件反序列化成一个对象:

image-20200605105345378

image-20200605105218619

上面代码CommandExec.java中在构造函数中我写的是执行calc计算器,这里本来我以为反序列化也可以弹出计算器,但是我的想法是错误的,查了下发现反序列不会调用构造方法,但是会调用父类构造方法,所以我这里改下CommandExec.java,让它继承一个父类,在父类中执行计算器。

image-20200605105751073

  • 改写后的CommandExec.java和父类:

父类Command.java:

Command.java
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
import java.io.BufferedReader;
import java.io.IOException;
import java.io.InputStream;
import java.io.InputStreamReader;

public class Command {
public Command(){
String cmd = "calc";
System.out.println("2222");
try {
Process process = Runtime.getRuntime().exec(cmd);
InputStream is = process.getInputStream();
InputStreamReader isr = new InputStreamReader(is);
BufferedReader br = new BufferedReader(isr);

for(String content = br.readLine(); content != null; content = br.readLine()) {
System.out.println(content);
}
} catch (IOException var7) {
var7.printStackTrace();
}

}
}

CommandExec.java

CommandExec.java
1
2
3
4
5
6
7
8
import java.io.*;

public class CommandExec extends Command implements Serializable{

public CommandExec() {
System.out.println("1111");
}
}

运行结果可以发现是对的,反序列化的时候确实执行了父类的构造方法 没有调用子类的构造方法。序列化之前构造对象时弹出了2个计算器,因为分别调用了父类和子类的构造方法,而反序列化之后生成对象也弹出了1个计算器,所以总共弹出3个计算器窗口。

image-20200605110928243

反序列化例子2:

后续学习遇到的困惑,这里更新下。

该例子重写了readObject()方法,当我们调用反序列化时会调用ReadObjectOverride.readObject()

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
27
28
import java.io.*;

public class ReadObjectTest {
public static void main(String[] args) throws Exception{
//创建一个ReadObjectOverride对象
ReadObjectOverride readObjectOverride = new ReadObjectOverride();
// 序列化
ByteArrayOutputStream baos = new ByteArrayOutputStream();
ObjectOutputStream oos = new ObjectOutputStream(baos);
oos.writeObject(readObjectOverride);
oos.flush();
oos.close();
// 反序列化
ByteArrayInputStream bais = new ByteArrayInputStream(baos.toByteArray());
ObjectInputStream ois = new ObjectInputStream(bais);
Object obj = (Object) ois.readObject();
}
}

class ReadObjectOverride implements Serializable{
//重写readObject()方法
private void readObject(java.io.ObjectInputStream in) throws IOException, ClassNotFoundException{
//执行默认的readObject()方法
in.defaultReadObject();
//执行打开计算器程序命令
Runtime.getRuntime().exec("calc");
}
}

运行结果:

image-20200728163524121

参考链接

https://baijiahao.baidu.com/s?id=1636492159314232573&wfr=spider&for=pc

https://www.cnblogs.com/niceyoo/p/10596657.html

https://www.itranslater.com/qa/details/2582634340228269056

https://blog.csdn.net/weixin_29324013/article/details/80901275

https://paper.seebug.org/312/