Java序列化系列教程(上)
一定义以及相关概念
互联网的产生带来了机器间通讯的需求,而互联通讯的双方需要采用约定的协议,序列化和反序列化属于通讯协议的一部分。通讯协议往往采用分层模型,不同模型每层的功能定义以及颗粒度不同,例如:TCP/IP协议是一个四层协议,而OSI模型却是七层协议模型。在OSI七层协议模型中展现层(Presentation Layer)的主要功能是把应用层的对象转换成一段连续的二进制串,或者反过来,把二进制串转换成应用层的对象,这两个功能就是序列化和反序列化。一般而言,TCP/IP协议的应用层对应与OSI七层协议模型的应用层,展示层和会话层,所以序列化协议属于TCP/IP协议应用层的一部分。本文对序列化协议的讲解主要基于OSI七层协议模型。
-
序列化: 将数据结构或对象转换成二进制串的过程。
-
反序列化:将在序列化过程中所生成的二进制串转换成数据结构或者对象的过程。
二应用场景2.1对象持久化
Java平台允许我们在内存中创建可复用的Java对象,但一般情况下,只有当JVM处于运行时,这些对象才可能存在,即,这些对象的生命周期不会比JVM的生命周期更长。但在现实应用中,就可能要求在JVM停止运行之后能够保存(持久化)指定的对象,并在将来重新读取被保存的对象。Java对象序列化就能够帮助我们实现该功能。在实际的生产场景中很多情况下会用到缓存,用到redis来实现缓存的都知道,在往缓存里放数据的时候不管是key还是value都要首先完成序列化,只有序列化后才能依靠redis来存放,当获取数据的时候要反序列化。
2.2对象网络传输
当在实际中使用RMI(远程方法调用)的时候,比如hession调用,dubbo调用,或在网络中传递对象时,都会用到对象序列化,和反序列化。
三如何实现序列化
在Java中,只要一个类实现了java.io.Serializable接口,那么它就可以被序列化。首先定义一个User类。
1package com.duomeng.product;
2import java.io.*;
3public class User implements Serializable{
4 private static final long serialVersionUID = -6513548580766215238L;
5 public String name;
6 public String pawssWord;
7 public String getName() {
8 return name;
9 }
10 public void setName(String name) {
11 this.name = name;
12 }
13 public String getPawssWord() {
14 return pawssWord;
15 }
16 public void setPawssWord(String pawssWord) {
17 this.pawssWord = pawssWord;
18 }
19}
接着写一个测试类来完成序列化和反序列化的测试,就定义为UserTest.class
1package com.duomen.product;
2import java.io.*;
3public class UserTest {
4 public static void main(String[] args) {
5 User user = new User();
6 user.setName("duomeng");
7 user.setPawssWord("123qwe");
8 try {
9 //序列化
10 ObjectOutputStream objectOutputStream = new ObjectOutputStream(new FileOutputStream("user.obj"));
11 objectOutputStream.writeObject(user);
12 objectOutputStream.flush();
13 objectOutputStream.close();
14 //反序列化
15 ObjectInputStream objectInputStream = new ObjectInputStream(new FileInputStream("user.obj"));
16 User oldUser = (User) objectInputStream.readObject();
17 objectInputStream.close();
18 System.out.println("name = [" + oldUser.getName() + "]");
19 System.out.println("password = [" + oldUser.getPawssWord() + "]");
20 } catch (IOException e) {
21 e.printStackTrace();
22 } catch (ClassNotFoundException e) {
23 e.printStackTrace();
24 }
25 }
26}
如果没有什么的问题的,那么结果也一定不出所料的输出如下内容
1name = [duomeng]
2password = [123qwe]
四相关知识
1.通过ObjectOutputStream和ObjectInputStream对对象进行序列化及反序列化
2.一个类如果被实例化,那么这个类必须实现java.io.Serializable,否在在实例化的时候会抛出java.io.NotSerializableException异常,因为在序列化的过程中大概有如下的调用列writeObject ---> writeObject0 --->writeOrdinaryObject--->writeSerialData--->invokeWriteObject,可以看下一下writeObject0方法里的部分代码,如下
1// remaining cases
2 if (obj instanceof String) {
3 writeString((String) obj, unshared);
4 } else if (cl.isArray()) {
5 writeArray(obj, desc, unshared);
6 } else if (obj instanceof Enum) {
7 writeEnum((Enum<?>) obj, desc, unshared);
8 } else if (obj instanceof Serializable) {
9 writeOrdinaryObject(obj, desc, unshared);
10 } else {
11 if (extendedDebugInfo) {
12 throw new NotSerializableException(
13 cl.getName() + "\n" + debugInfoStack.toString());
14 } else {
15 throw new NotSerializableException(cl.getName());
16 }
17 }
3.虚拟机是否允许反序列化,不仅取决于类路径和功能代码是否一致,一个非常重要的一点是两个类的序列化 ID 是否一致(就是 private static final long serialVersionUID)如果不显示的指定序列化Id,那么在序列化的过程中jvm会根据当前的对象的类的属性和方法计算出一个serialVersionUID,同样当反序列化的时候也会根据目标类的属性和方法结算一个serialVersionUID,如果在序列化之后,对应的类的属性或者方法发生变化那么计算出来的serialVersionUID是不一样的,这样在反序列化的时候就会抛异常,反序列化失败,通常的做法是通过IDE来显示的生成,这样不仅可以防止修改类的属性或者方法导致反序列化失败,从而也可以依靠serialVersionUID的不同来对一些访问做限制,比如变化了serialVersionUID之后,那些不知道最新serialVersionUID的访问将会失败。
4.要想将父类对象也序列化,就需要让父类也实现java.io.Serializable接口,否则父类的属性并不能完成序列化。
五总结
在实际的应用当中序列化的实现还有很多,比如通常用到的json和xml也同样是一种序列化,就像开头所说的只要是将数据结构或对象转换成二进制串的过程就叫序列化,只要是将在序列化过程中所生成的二进制串转换成数据结构或者对象的过程就是反序列化。