目录
序列化的定义
序列化:把对象转化为可传输的字节序列过程称为序列化。
反序列化:把字节序列还原为对象的过程称为反序列化。
为什么要序列化?
如果光看定义我想你很难一下子理解序列化的意义,那么我们可以从另一个角度来推导出什么是序列化, 那么究竟序列化的目的是什么?
其实序列化最终的目的是为了对象可以跨平台存储,和进行网络传输。而我们进行跨平台存储和网络传输的方式就是IO,而我们的IO支持的数据格式就是字节数组。
因为我们单方面的只把对象转成字节数组还不行,因为没有规则的字节数组我们是没办法把对象的本来面目还原回来的,所以我们必须在把对象转成字节数组的时候就制定一种规则(序列化),那么我们从IO流里面读出数据的时候再以这种规则把对象还原回来(反序列化)。
如果我们要把一栋房子从一个地方运输到另一个地方去,序列化就是我把房子拆成一个个的砖块放到车子里,然后留下一张房子原来结构的图纸,反序列化就是我们把房子运输到了目的地以后,根据图纸把一块块砖头还原成房子原来面目的过程
什么情况下需要序列化?
通过上面我想你已经知道了凡是需要进行“跨平台存储”和”网络传输”的数据,都需要进行序列化。
本质上存储和网络传输 都需要经过 把一个对象状态保存成一种跨平台识别的字节格式,然后其他的平台才可以通过字节信息解析还原对象信息。
序列化的方式
序列化只是一种拆装组装对象的规则,那么这种规则肯定也可能有多种多样,比如现在常见的序列化方式有:
JDK(不支持跨语言)、JSON、XML、Hessian、Kryo(不支持跨语言)、Thrift、Protostuff、FST(不支持跨语言)
序列化技术选型的几个关键点
序列化协议各有千秋,不能简单的说一种序列化协议是最好的,只能从你的当时环境下去选择最适合你们的序列化协议,如果你要为你的公司项目进行序列化技术的选型,那么主要从以下几个因素。
协议是否支持跨平台
如果你们公司有好多种语言进行混合开发,那么就肯定不适合用有语言局限性的序列化协议,要不然你JDK序列化出来的格式,其他语言并没法支持。
序列化的速度
如果序列化的频率非常高,那么选择序列化速度快的协议会为你的系统性能提升不少。
序列化出来的大小
如果频繁的在网络中传输的数据那就需要数据越小越好,小的数据传输快,也不占带宽,也能整体提升系统的性能。
Java 是如何实现序列化的?
前面主要介绍了一下什么是序列化,那么下面主要讲下JAVA是如何进行序列化的,以及序列化的过程中需要注意的一些问题
-
java 实现序列化很简单,只需要实现Serializable 接口即可。
public class User implements Serializable {
private String name;
private int age;
private int status;
public String getName() {
return name;
}
public void setName(String name) {
this.name = name;
}
public int getAge() {
return age;
}
public void setAge(int age) {
this.age = age;
}
public int getStatus() {
return status;
}
public void setStatus(int status) {
this.status = status;
}
@Override
public String toString() {
return "User{" +
"name='" + name + '\'' +
", age=" + age +
", status=" + status +
'}';
}
}
-
把User对象设置值后写入文件
public static void main(String[] args) throws Exception {
FileOutputStream fos = new FileOutputStream("D:\\test.txt");
ObjectOutputStream oos = new ObjectOutputStream(fos);
User user = new User();
user.setName("张三");
user.setAge(3);
user.setStatus(1);
oos.writeObject(user);
oos.flush();
oos.close();
}
-
再把从文件读取出来的转换为对象
public static void main(String[] args) throws Exception {
FileInputStream fis = new FileInputStream("D:\\test.txt");
ObjectInputStream ois = new ObjectInputStream(fis);
User user = (User) ois.readObject();
System.out.println("User=>"+user);
}
输出结果为:
以上把User对象进行二进制的数据存储后,并从文件中读取数据出来转成User对象就是一个序列化和反序列化的过程。
JAVA序列化中常见的问题
-
问题一:static 属性不能被序列化
原因:序列化保存的是对象的状态,静态变量属于类的状态,因此 序列化并不保存静态变量。
-
问题二:Transient 属性不会被序列化
接着上面的案例,我们在User里面加上一个transient 状态的心情属性gender;
public class User implements Serializable {
private String name;
private int age;
private transient String gender;
private int status;
public String getGender() {
return gender;
}
public void setGender(String gender) {
this.gender = gender;
}
public String getName() {
return name;
}
public void setName(String name) {
this.name = name;
}
public int getAge() {
return age;
}
public void setAge(int age) {
this.age = age;
}
public int getStatus() {
return status;
}
public void setStatus(int status) {
this.status = status;
}
@Override
public String toString() {
return "User{" +
"name='" + name + '\'' +
", age=" + age +
", gender=" + gender +
", status=" + status +
'}';
}
}
把User对象设置值后写入文件
public static void main(String[] args) throws Exception {
FileOutputStream fos = new FileOutputStream("D:\\test.txt");
ObjectOutputStream oos = new ObjectOutputStream(fos);
User user = new User();
user.setName("张三");
user.setGender("男");
user.setAge(3);
user.setStatus(1);
oos.writeObject(user);
oos.flush();
oos.close();
}
再把从文件读取出来的转换为对象并打印User对象的值
public static void main(String[] args) throws Exception {
FileInputStream fis = new FileInputStream("D:\\test.txt");
ObjectInputStream ois = new ObjectInputStream(fis);
User user = (User) ois.readObject();
System.out.println("User=>"+user);
}
输出结果为:(原生类型为对应类型的默认值,包装类型为null)
但是如果gender是int类型的话返回并不会是null而是返回0
接着上面的案例,我们在User里面加上一个transient 状态的心情属性gender并设置为int类型;
public class User implements Serializable {
private String name;
private int age;
private transient int gender;
private int status;
public int getGender() {
return gender;
}
public void setGender(int gender) {
this.gender = gender;
}
public String getName() {
return name;
}
public void setName(String name) {
this.name = name;
}
public int getAge() {
return age;
}
public void setAge(int age) {
this.age = age;
}
public int getStatus() {
return status;
}
public void setStatus(int status) {
this.status = status;
}
@Override
public String toString() {
return "User{" +
"name='" + name + '\'' +
", age=" + age +
", gender=" + gender +
", status=" + status +
'}';
}
}
把User对象设置值后写入文件
public static void main(String[] args) throws Exception {
FileOutputStream fos = new FileOutputStream("D:\\test.txt");
ObjectOutputStream oos = new ObjectOutputStream(fos);
User user = new User();
user.setName("张三");
user.setGender(1);
user.setAge(3);
user.setStatus(1);
oos.writeObject(user);
oos.flush();
oos.close();
}
再把从文件读取出来的转换为对象并打印User对象的值
public static void main(String[] args) throws Exception {
FileInputStream fis = new FileInputStream("D:\\test.txt");
ObjectInputStream ois = new ObjectInputStream(fis);
User user = (User) ois.readObject();
System.out.println("User=>"+user);
}
输出结果为:(原生类型为对应类型的默认值,包装类型为0)
-
问题三:序列化版本号serialVersionUID
所有实现序列化的对象都必须要有个版本号,这个版本号可以由我们自己定义,当我们没定义的时候JDK工具会按照我们对象的属性生成一个对应的版本号。
版本号有什么用?
其实这个版本号就和我们平常软件的版本号一样,你的软件版本号和官方的服务器版本不一致的话就告诉你有新的功能更新了,主要用于提示用户进行更新。序列化也一样,我们的对象通常需要根据业务的需求变化要新增、修改或者删除一些属性,在我们做了一些修改后,就通过修改版本号告诉 反序列化的那一方对象有了修改你需要同步修改。
使用JDK生成的版本号和我们自定义的版本号的区别?
JDK工具生成的serialVersionUID 是根据对象的属性信息生成的一个编号,这就意味着只要对象的属性有一点变动那么他的序列化版本号就会同步进行改变。
这种情况有时候就不太友好,就像我们的软件一样,使用JDK生成的serialVersionUID,只要对象有一丁点改变serialVersionUID就会随着变更,这样的话用户就得强制更新软件的版本,用户不更新就使用不了软件。
而大多数友好的情况也许是这样的,用户可以选择不更新,不更新的话用户只是无法体验新加的功能而已。
而这种方式就需要我们自定义的版本号了,这样我就可以在新增了属性后不修改serialVersionUID,反序列化的时候只是无法或许新加的属性,并不影响程序运行。
下面用代码测试一下我们的理论:
(1)对象属性序列化版本号不同进行序列化和反序列化
继上面的例子
序列化之前我们设置serialVersionUID=2
public class User implements Serializable {
private static final long serialVersionUID=2;
private String name;
private int age;
private transient int gender;
private int status;
public int getGender() {
return gender;
}
public void setGender(int gender) {
this.gender = gender;
}
public String getName() {
return name;
}
public void setName(String name) {
this.name = name;
}
public int getAge() {
return age;
}
public void setAge(int age) {
this.age = age;
}
public int getStatus() {
return status;
}
public void setStatus(int status) {
this.status = status;
}
@Override
public String toString() {
return "User{" +
"name='" + name + '\'' +
", age=" + age +
", gender=" + gender +
", status=" + status +
'}';
}
}
序列化存储User到text.txt
public static void main(String[] args) throws Exception {
FileOutputStream fos = new FileOutputStream("D:\\test.txt");
ObjectOutputStream oos = new ObjectOutputStream(fos);
User user = new User();
user.setName("张三");
user.setGender(1);
user.setAge(3);
user.setStatus(1);
oos.writeObject(user);
oos.flush();
oos.close();
}
然后我们反序列化的时候对象的版本号是serialVersionUID=1
public class User implements Serializable {
private static final long serialVersionUID=1;
private String name;
private int age;
private transient int gender;
private int status;
public int getGender() {
return gender;
}
public void setGender(int gender) {
this.gender = gender;
}
public String getName() {
return name;
}
public void setName(String name) {
this.name = name;
}
public int getAge() {
return age;
}
public void setAge(int age) {
this.age = age;
}
public int getStatus() {
return status;
}
public void setStatus(int status) {
this.status = status;
}
@Override
public String toString() {
return "User{" +
"name='" + name + '\'' +
", age=" + age +
", gender=" + gender +
", status=" + status +
'}';
}
}
最后再把从文件数据反序列化取出来
public static void main(String[] args) throws Exception {
FileInputStream fis = new FileInputStream("D:\\test.txt");
ObjectInputStream ois = new ObjectInputStream(fis);
User user = (User) ois.readObject();
System.out.println("User=>"+user);
}
最后执行结果反序列化异常,原因是对象序列化和反序列化的版本号不同导致
(2)对象新增属性,但是版本号相同也可以反序列化成功
序列化的对象信息 这里比反序列化的对象多了个ack
属性
public class User implements Serializable {
private static final long serialVersionUID=1;
private String name;
private int age;
private transient int gender;
private int status;
private int ack;
public int getAck() {
return ack;
}
public void setAck(int ack) {
this.ack = ack;
}
public int getGender() {
return gender;
}
public void setGender(int gender) {
this.gender = gender;
}
public String getName() {
return name;
}
public void setName(String name) {
this.name = name;
}
public int getAge() {
return age;
}
public void setAge(int age) {
this.age = age;
}
public int getStatus() {
return status;
}
public void setStatus(int status) {
this.status = status;
}
@Override
public String toString() {
return "User{" +
"name='" + name + '\'' +
", age=" + age +
", gender=" + gender +
", status=" + status +
", ack=" + ack +
'}';
}
}
序列化存储User到text.txt
public static void main(String[] args) throws Exception {
FileOutputStream fos = new FileOutputStream("D:\\test.txt");
ObjectOutputStream oos = new ObjectOutputStream(fos);
User user = new User();
user.setName("张三");
user.setGender(1);
user.setAge(3);
user.setStatus(1);
user.setAck(12);
oos.writeObject(user);
oos.flush();
oos.close();
}
反序列化的对象信息
序列化的对象信息 这里比序列化的对象少了个ack
属性,但版本号一致
public class User implements Serializable {
private static final long serialVersionUID=1;
private String name;
private int age;
private transient int gender;
private int status;
public int getGender() {
return gender;
}
public void setGender(int gender) {
this.gender = gender;
}
public String getName() {
return name;
}
public void setName(String name) {
this.name = name;
}
public int getAge() {
return age;
}
public void setAge(int age) {
this.age = age;
}
public int getStatus() {
return status;
}
public void setStatus(int status) {
this.status = status;
}
}
最后再把从文件数据反序列化取出来
public static void main(String[] args) throws Exception {
FileInputStream fis = new FileInputStream("D:\\test.txt");
ObjectInputStream ois = new ObjectInputStream(fis);
User user = (User) ois.readObject();
System.out.println("User=>"+user);
}
最后控制台打印结果正常
结果证明,只要序列化版本一样,对象新增属性并不会影响反序列化对象。
(3)对象新增属性,但是版本号是使用的JDK生成序列化版本号
省略代码,最后执行结果报错,原因是序列化和反序列化的版本号不一致造成。
-
问题四:父类、子类序列化问题
序列化是以正向递归的形式进行的,如果父类实现了序列化那么其子类都将被序列化;子类实现了序列化而父类没实现序列化,那么只有子类的属性会进行序列化,而父类的属性是不会进行序列化的。
(1)父类没有实现序列化,子类实现序列化
父类
public class Parent {
private String pName;
public String getpName() {
return pName;
}
public void setpName(String pName) {
this.pName = pName;
}
}
子类
public class User extends Parent implements Serializable {
private static final long serialVersionUID=1;
private String name;
private int age;
private transient int gender;
private int status;
public int getGender() {
return gender;
}
public void setGender(int gender) {
this.gender = gender;
}
public String getName() {
return name;
}
public void setName(String name) {
this.name = name;
}
public int getAge() {
return age;
}
public void setAge(int age) {
this.age = age;
}
public int getStatus() {
return status;
}
public void setStatus(int status) {
this.status = status;
}
@Override
public String toString() {
return "User{" +
"name='" + name + '\'' +
", age=" + age +
", gender=" + gender +
", status=" + status +
'}';
}
}
序列化后
public static void main(String[] args) throws Exception {
FileOutputStream fos = new FileOutputStream("D:\\test.txt");
ObjectOutputStream oos = new ObjectOutputStream(fos);
User user = new User();
user.setName("张三");
user.setGender(1);
user.setAge(3);
user.setStatus(1);
user.setpName("父类");
oos.writeObject(user);
oos.flush();
oos.close();
}
再反序列化
public static void main(String[] args) throws Exception {
FileInputStream fis = new FileInputStream("D:\\test.txt");
ObjectInputStream ois = new ObjectInputStream(fis);
User user = (User) ois.readObject();
System.out.println("pName=>"+user.getpName());
}
最后执行结果,父类属性未被序列化
(2)父类实现序列化,子类不实现序列化
父类
public class Parent implements Serializable {
private String pName;
public String getpName() {
return pName;
}
public void setpName(String pName) {
this.pName = pName;
}
}
子类
public class User extends Parent {
private static final long serialVersionUID=1;
private String name;
private int age;
private transient int gender;
private int status;
public int getGender() {
return gender;
}
public void setGender(int gender) {
this.gender = gender;
}
public String getName() {
return name;
}
public void setName(String name) {
this.name = name;
}
public int getAge() {
return age;
}
public void setAge(int age) {
this.age = age;
}
public int getStatus() {
return status;
}
public void setStatus(int status) {
this.status = status;
}
@Override
public String toString() {
return "User{" +
"name='" + name + '\'' +
", age=" + age +
", gender=" + gender +
", status=" + status +
'}';
}
}
序列化后
public static void main(String[] args) throws Exception {
FileOutputStream fos = new FileOutputStream("D:\\test.txt");
ObjectOutputStream oos = new ObjectOutputStream(fos);
User user = new User();
user.setName("张三");
user.setGender(1);
user.setAge(3);
user.setStatus(1);
user.setpName("父类");
oos.writeObject(user);
oos.flush();
oos.close();
}
再反序列化
public static void main(String[] args) throws Exception {
FileInputStream fis = new FileInputStream("D:\\test.txt");
ObjectInputStream ois = new ObjectInputStream(fis);
User user = (User) ois.readObject();
System.out.println("pName=>"+user.getpName());
}
最后执行结果,子类属性序列化正常