本文参考自java 序列化,看这篇就够了

文章目录

  • 一、序列化的定义
  • 二、实现序列化的方式
    • 1. Serializable
      • 父类与子类的序列化关系
      • 成员是引用的序列化机制
      • 同一对象多次序列化机制
        • 潜在问题
      • 自定义序列化
    • 2. Externalizable
    • 3. 两种序列化对比
  • 序列化版本号
  • 总结

一、序列化的定义

  • 序列化:将对象写入到 IO 流中
  • 反序列化:从 IO 流中恢复对象
  • 使用场景:将对象存入数据库或文件时,在网络通信时传输序列化后的对象时

二、实现序列化的方式

如果一个类想要实现序列化,那它就要实现Serializable或者Externalizable两个接口中的一个。这里放个 User 类 用来序列化:

User 类

package com.grh;
import java.io.Serializable;
public class User implements Serializable {private String name;private String password;//get/set省略public User() {System.out.println("一个对象被创建了");}public User(String name, String password) {System.out.println("一个对象被创建了");this.name = name;this.password = password;}@Overridepublic String toString() {return "User{" +"name='" + name + '\'' +", password='" + password + '\'' +'}';}
}

1. Serializable

相信对序列化好奇的人都点开过他的源码,然而他的源码是空的,只是一个没有任何函数和属性的接口:

public interface Serializable {}

那么实现这个接口有什么用呢?

它只是个标记接口,用来提醒 JVM 这个类可以序列化,也就是说, Java 将类是否实现序列化的选择交给了程序员。

我们看个例子,它将 User 类进行序列化,并写入了 User.txt 文件中,然后再从中读出来进行输出。

Test 测试类

package com.grh;
import org.junit.Test;
import java.io.*;
public class serialize {@Testpublic void serializeBySerializable() {try {ObjectOutputStream oos = new ObjectOutputStream(new FileOutputStream("User.txt"));User user = new User("张三","123");oos.writeObject(user);oos.close();ObjectInputStream ois = new ObjectInputStream(new FileInputStream("User.txt"));User user1 = (User)ois.readObject();ois.close();System.out.println(user1);} catch (FileNotFoundException e) {e.printStackTrace();} catch (IOException e) {e.printStackTrace();} catch (ClassNotFoundException e) {e.printStackTrace();}}
}

输出结果:

一个对象被创建了
User{name='张三', password='123'}

我们打开项目所在位置,会发现多了一个 User.txt 文件,里面的内容如下

这就是通过字节流序列化后的 User 对象,我们可以看到他的类名:com.grh.User ,也就是说,序列化会将该对象的类的信息也存进去。

还有一个地方值得注意,输出结果中显示只调用了一次构造函数,也就是通过 new 创建对象时创建的,也就是说,JVM 有自己的方式反序列化来创建对象,而不需要通过构造函数。

父类与子类的序列化关系

  1. 如果一个类的父类实现了序列化,那么这个类也可以被实例化

  2. 如果一个子类实现了序列化,但父类没有实现,那么将子类反序列化时,会调用父类的无参构造函数构建父类对象,而且父类中的数据都是默认值,如 int 为 0 ,对象为 null。

我们定义一个 Person 类,并用 User 类继承它。

package com.grh;
import java.io.Serializable;
public class Person {Person(){System.out.println("构建一个person");}
}

再次调用会得到一下结果:

构建一个person
一个对象被创建了
构建一个person
User{name='张三', password='123'}

我们可以看到父类 Person 的构造函数调用了两次,一次是 new 的时候,一次是反序列化 user 的时候。

成员是引用的序列化机制

如果一个可序列化的类的成员不是基本类型,也不是 String 类型,那这个引用类型也必须是可序列化的;否则,会导致此类不能序列化。

同一对象多次序列化机制

我们去掉 User 和 Person 的继承关系,将 User 作为 Person 中的一个成员,再加个年龄的成员,Person 类如下

package com.grh;
import java.io.Serializable;
public class Person implements Serializable{private int age;private User user;public Person(){ }public Person(int age, User user) {this.age = age;this.user = user;}public int getAge() { return age; }public void setAge(int age) { this.age = age; }public User getUser() { return user; }public void setUser(User user) { this.user = user; }
}

再写一个测试类,注意:反序列化的顺序与序列化时的顺序一致:

    @Testpublic void serializeBySerializable() {try {ObjectOutputStream oos = new ObjectOutputStream(new FileOutputStream("User.txt"));User setUser = new User("张三","123");Person setPerson = new Person(18,setUser);Person setPerson1 = new Person(25,setUser);oos.writeObject(setUser);oos.writeObject(setPerson);oos.writeObject(setPerson1);oos.writeObject(setPerson1);oos.close();ObjectInputStream ois = new ObjectInputStream(new FileInputStream("User.txt"));User getUser = (User)ois.readObject();Person getPerson = (Person)ois.readObject();Person getPerson1 = (Person)ois.readObject();Person getPerson2 = (Person)ois.readObject();ois.close();System.out.println(getPerson==getPerson2);     //falseSystem.out.println(getPerson.getUser()==getPerson1.getUser());    //trueSystem.out.println(getUser==getPerson1.getUser());    //trueSystem.out.println(getUser==getPerson2.getUser());    //true} catch (FileNotFoundException e) {e.printStackTrace();} catch (IOException e) {e.printStackTrace();} catch (ClassNotFoundException e) {e.printStackTrace();}}

可以看到,不管怎么存 user ,读出来时也都是同一个,而年龄不同的 person 是不同的对象,读出来时也是不同的。

Java 序列化同一对象,并不会将此对象序列化多次得到多个对象。

潜在问题

每个序列化的对象都有一个编号,如果多次序列化,只会重复这个编号,并不会重新序列化。

因此,如果序列化一个可变对象(对象内的内容可更改)后,更改了对象内容,再次序列化,只是保存序列化编号,会导致更改无效化。如:

    @Testpublic void serializeBySerializable() {try {ObjectOutputStream oos = new ObjectOutputStream(new FileOutputStream("User.txt"));User setUser = new User("张三","123");oos.writeObject(setUser);setUser.setName("王五");oos.writeObject(setUser);oos.close();ObjectInputStream ois = new ObjectInputStream(new FileInputStream("User.txt"));User getUser = (User)ois.readObject();ois.close();System.out.println(getUser);} catch (FileNotFoundException e) {e.printStackTrace();} catch (IOException e) {e.printStackTrace();} catch (ClassNotFoundException e) {e.printStackTrace();}}

输出结果:

User{name='张三', password='123'}

可以看到修改名字为王五并没有实现。

自定义序列化

  • static 不会被序列化,因为序列化存的是对象,static 所代表的是类
  • 在成员前加上 transient 关键字,就不会对这个成员进行序列化,反序列化时还会有这个成员,不过会赋值默认值。
  • 重写 writeObject 与 readObject 方法,就是修改序列化的规则,比 transient 更灵活,还可以对数据进行加密,提高安全性。注意,writeObject 与 readObject 方法需要对应,怎么进行序列化,就应该怎样反序列化。如:
public class Person implements Serializable {private String name;private int age;//省略构造方法,get及set方法private void writeObject(ObjectOutputStream out) throws IOException {//将名字反转写入二进制流out.writeObject(new StringBuffer(this.name).reverse());out.writeInt(age);}private void readObject(ObjectInputStream ins) throws IOException, ClassNotFoundException {//将读出的字符串反转恢复回来this.name = ((StringBuffer) ins.readObject()).reverse().toString();this.age = ins.readInt();}
}
  • 重写 writeReplace 和 readResolve 方法,这两个方法会在写入之前调用,一般用于替换掉要写入或读取的对象。
package com.grh;
import java.io.*;
import java.util.ArrayList;
public class Person implements Serializable {private String name;private int age;public Person(String name, int age) {this.name = name;this.age = age;}private Object writeReplace() throws ObjectStreamException {ArrayList<Object> list = new ArrayList<Object>(2);list.add(this.name);list.add(this.age);return list;}public static void main(String[] args) {try {ObjectOutputStream oos = new ObjectOutputStream(new FileOutputStream("person.txt"));ObjectInputStream ios = new ObjectInputStream(new FileInputStream("person.txt"));Person person = new Person("张三", 23);oos.writeObject(person);ArrayList list = (ArrayList) ios.readObject();System.out.println(list);} catch (FileNotFoundException e) {e.printStackTrace();} catch (IOException e) {e.printStackTrace();} catch (ClassNotFoundException e) {e.printStackTrace();}}
}
//输出结果:
//[张三, 23]

在上面的例子中,本来写入的是 Person,但换成了 list,读取出来的时候也可以看到已经是 list 类型而不是 Person 类型了。

package com.grh;
import java.io.*;
import java.util.HashMap;public class Person implements Serializable {private String name;private int age;public Person(String name, int age) {this.name = name;this.age = age;}@Overridepublic String toString() {return "Person{" +"name='" + name + '\'' +", age=" + age +'}';}private Object readResolve() throws ObjectStreamException {return new Person("李四", 23);}public static void main(String[] args){try {ObjectOutputStream oos = new ObjectOutputStream(new FileOutputStream("person.txt"));ObjectInputStream ios = new ObjectInputStream(new FileInputStream("person.txt"));Person person1 = new Person("张三", 23);Person person2 = new Person("王五", 23);oos.writeObject(person1);oos.writeObject(person2);Person person3 = (Person) ios.readObject();Person person4 = (Person) ios.readObject();System.out.println(person3);System.out.println(person4);System.out.println(person3==person4);} catch (FileNotFoundException e) {e.printStackTrace();} catch (IOException e) {e.printStackTrace();} catch (ClassNotFoundException e) {e.printStackTrace();}}
}

在这个类中,无论序列化了多少个对象进入文件中,当他读出的时候,始终会调用 readResolve() 方法 new 一个李四的对象出来,序列化中的数据已经不重要了。

一般来说,重写 readResolve() 主要用于防止破坏单例模式的规则,因为反序列化后会重新构建一个一样的对象,如果在 readResolve() 方法中直接返回已经存在的单例对象,反序列化后就不会生成对象了。

注:writeReplace() 在writeObject前调用,readResolve() 在 readObject() 后调用。

2. Externalizable

Externalizable接口中,有两个方法,writeExternal() 和 readExternal() ,因此要想实现这个接口来序列化,必须实现这两个函数

package com.grh;
import java.io.*;
public class ExPerson implements Externalizable {private String name;private int age;//注意,必须加上pulic 无参构造器public ExPerson() {  System.out.println("无参构造函数构建对象");}public ExPerson(String name, int age) {this.name = name;this.age = age;}@Overridepublic String toString() {return "ExPerson{" +"name='" + name + '\'' +", age=" + age +'}';}public void writeExternal(ObjectOutput out) throws IOException {//将name反转后写入二进制流StringBuffer reverse = new StringBuffer(name).reverse();System.out.println(reverse.toString());out.writeObject(reverse);out.writeInt(age);}public void readExternal(ObjectInput in) throws IOException, ClassNotFoundException {//将读取的字符串反转后赋值给name实例变量this.name = ((StringBuffer) in.readObject()).reverse().toString();System.out.println(name);this.age = in.readInt();}public static void main(String[] args) {try {ObjectOutputStream oos = new ObjectOutputStream(new FileOutputStream("ExPerson.txt"));ObjectInputStream ois = new ObjectInputStream(new FileInputStream("ExPerson.txt"));oos.writeObject(new ExPerson("brady", 23));ExPerson ep = (ExPerson) ois.readObject();System.out.println(ep);} catch (FileNotFoundException e) {e.printStackTrace();} catch (IOException e) {e.printStackTrace();} catch (ClassNotFoundException e) {e.printStackTrace();}}
}

输出结果:

ydarb
无参构造函数构建对象
brady
ExPerson{name='brady', age=23}

这个例子跟 Serializable 接口中的自定义序列化基本一致,将用户名反转再序列化和反序列化。

注意:Externalizable接口不同于Serializable接口,实现此接口必须实现接口中的两个方法实现自定义序列化,这是强制性的;特别之处是必须提供pulic的无参构造器,因为在反序列化的时候需要反射创建对象。

3. 两种序列化对比

Serializable Externalizable
系统自动存储必要的信息 程序员决定存储哪些信息
Java内建支持,易于实现,只需要实现该接口即可,无需任何代码支持 必须实现接口内的两个方法
性能略差 性能略好

虽然Externalizable接口带来了一定的性能提升,但变成复杂度也提高了,所以一般通过实现Serializable接口进行序列化。

序列化版本号

java序列化提供了一个private static final long serialVersionUID 的序列化版本号,只有版本号相同,即使更改了序列化属性,对象也可以正确被反序列化回来。

如果反序列化使用的class的版本号与序列化时使用的不一致,反序列化会报InvalidClassException异常。

序列化版本号可自由指定,如果不指定,JVM会根据类信息自己计算一个版本号,这样随着class的升级,就无法正确反序列化;不指定版本号另一个明显隐患是,不利于jvm间的移植,可能class文件没有更改,但不同jvm可能计算的规则不一样,这样也会导致无法反序列化。

什么情况下需要修改serialVersionUID呢?分三种情况:

  • 如果只是修改了方法,反序列化不容影响,则无需修改版本号;
  • 如果只是修改了静态变量,瞬态变量(transient修饰的变量),反序列化不受影响,无需修改版本号;
  • 如果修改了非瞬态变量,则可能导致反序列化失败。如果新类中实例变量的类型与序列化时类的类型不一致,则会反序列化失败,这时候需要更改serialVersionUID。如果只是新增了实例变量,则反序列化回来新增的是默认值;如果减少了实例变量,反序列化时会忽略掉减少的实例变量。

总结

  • 所有需要网络传输的对象都需要实现序列化接口,通过建议所有的javaBean都实现Serializable接口。
  • 对象的类名、实例变量(包括基本类型,数组,对其他对象的引用)都会被序列化;方法、类变量、transient实例变量都不会被序列化。
  • 如果想让某个变量不被序列化,使用transient修饰。
  • 序列化对象的引用类型成员变量,也必须是可序列化的,否则,会报错。
  • 反序列化时必须有序列化对象的class文件。
  • 当通过文件、网络来读取序列化后的对象时,必须按照实际写入的顺序读取。
  • 单例类序列化,需要重写readResolve()方法;否则会破坏单例原则。
  • 同一对象序列化多次,只有第一次序列化为二进制流,以后都只是保存序列化编号,不会重复序列化。
  • 建议所有可序列化的类加上serialVersionUID 版本号,方便项目升级。

Java序列化反序列化详解相关推荐

  1. Java 序列化Serializable详解(附详细例子)

    Java 序列化Serializable详解(附详细例子) 1.什么是序列化和反序列化 Serialization(序列化)是一种将对象以一连串的字节描述的过程:反序列化deserialization ...

  2. Java 序列化Serializable详解

    转载 Java 序列化Serializable详解(附详细例子) 1.什么是序列化和反序列化 Serialization(序列化)是一种将对象以一连串的字节描述的过程:反序列化deserializat ...

  3. java Serializable和Externalizable序列化反序列化详解--转

    一.什么是序列化?   "对象序列化"(Object Serialization)是 Java1.1就开始有的特性. 简单地说,就是可以将一个对象(标志对象的类型)及其状态转换为字 ...

  4. php 序列化 java_PHP--序列化与反序列化详解

    PHP--序列化与反序列化详解 博客说明 文章所涉及的资料来自互联网整理和个人总结,意在于个人学习和经验汇总,如有什么地方侵权,请联系本人删除,谢谢! 说明 所有php里面的值都可以使用函数seria ...

  5. 4.6 W 字总结!Java 11—Java 17特性详解

    作者 | 民工哥技术之路 来源 | https://mp.weixin.qq.com/s/SVleHYFQeePNT7q67UoL4Q Java 11 特性详解 基于嵌套的访问控制 与 Java 语言 ...

  6. java io流详解_一文带你看懂JAVA IO流,史上最全面的IO教学啦

    一.IO流是什么 惯例引用百科的回答流是一种抽象概念,它代表了数据的无结构化传递.按照流的方式进行输入输出,数据被当成无结构的字节序或字符序列.从流中取得数据的操作称为提取操作,而向流中添加数据的操作 ...

  7. php中对象怎么访问i属性_PHP--序列化与反序列化详解

    PHP--序列化与反序列化详解 博客说明 文章所涉及的资料来自互联网整理和个人总结,意在于个人学习和经验汇总,如有什么地方侵权,请联系本人删除,谢谢! 说明 学到网络安全的时候用到了序列化和反序列化的 ...

  8. PHP反序列化详解(二)——PHP魔术方法与PHP反序列化漏洞

    今天继续给大家介绍渗透测试相关知识,本文主要内容是PHP反序列化详解(二)--PHP魔术方法与PHP反序列化漏洞. 免责声明: 本文所介绍的内容仅做学习交流使用,严禁利用文中技术进行非法行为,否则造成 ...

  9. Java内存溢出详解之Tomcat配置

    Java内存溢出详解 转自:http://elf8848.iteye.com/blog/378805 一.常见的Java内存溢出有以下三种: 1. java.lang.OutOfMemoryError ...

  10. java基础(十三)-----详解内部类——Java高级开发必须懂的

    java基础(十三)-----详解内部类--Java高级开发必须懂的 目录 为什么要使用内部类 内部类基础 静态内部类 成员内部类 成员内部类的对象创建 继承成员内部类 局部内部类 推荐博客 匿名内部 ...

最新文章

  1. 清华大学张悠慧:超越冯·诺依曼模型,实现软硬件去耦合的类脑计算(附视频)
  2. 2019牛客暑期多校训练营(第五场)G-subsequence 1
  3. STM32休眠后不能烧录程序
  4. 数字图像处理与Python实现笔记之基础知识
  5. win10 HADOOP_HOME and hadoop.home.dir are unset
  6. 世界上没有一模一样的东西_世界上存在两根同时点燃同时燃尽一模一样的蜡烛吗?...
  7. win10 java无法运行_Win10中配置jdk之后javac无法运行
  8. oracle 查看锁死的表
  9. 操作excel方便么_【Excel好推荐】专业仪表板
  10. 第六届中国云计算大会详细日程
  11. HRBEU 字符串 1003
  12. “阿里灵杰”问天引擎电商搜索算法赛 - 数据说明2022
  13. qq空间尾巴怎么修改成别的机型
  14. c++ win10下 遍历文件夹的方式, dirent.h头文件缺失问题
  15. 前端页面预览word_js打开word文档预览操作示例【不是下载】
  16. Spark面试近300题初始版本
  17. 一生践行“心正则字正”
  18. 设计电商网站必看,如何改善用户体验
  19. 新能源与自动驾驶汽车市场
  20. python strptime_Python法律实务应用——制作自己的LPR计算器

热门文章

  1. Pytorch中autograd.Variable.backward的grad_varables参数个人理解浅见
  2. vue中使用dialog内容修改,表格也跟着修改问题解决
  3. 如何在电脑上播放iso映像文件
  4. 原创:猎头公司信息化方案一、二期
  5. Mybatis-Mapper返回对象数据为空,报空指针异常引起原因
  6. 点亮你的证件照!自制背景颜色修改脚本揭秘!
  7. 如何万网域名解析亚马逊服务器,Amazon Route 53
  8. 大一python选择题题库答案_Python复习题库带答案
  9. CSDN博客阅读量大于100万/次 大牛排行榜
  10. 【SQLITE3】基于SQLITE3的银行储蓄管理系统