序列化与反序列化

序列化:将对象的状态信息转换为可以存储或传输的数据形式(比如二进制)的过程。
反序列化:与序列化相对,把序列化转换成的可以存储或传输的数据形式转化为对象的状态信息的过程。

java序列化与反序列化

序列化:把对象转换为二进制流。
反序列化:把二进制流数据转化为对象。

使用场景

  1. 永久保存数据。把序列化后的数据保存到磁盘等存储设备中。使用时可以反序列化。比如某些对象不想随着JVM关闭而消失,可以序列化到磁盘中。
  2. 用于网络传输。把对象序列化为二进制数据,传输到远程计算机。
  3. java序列化还可以实现对象深拷贝。

使用方式

java实现序列化与反序列化需要ObjectInputStrem(反序列化)和ObjectOutputStream(序列化)。并且序列化对象需要实现Serializable接口。

代码:


//实现Serializable接口
public class Person implements Serializable{private static final long serialVersionUID = 2063917965595163411L;private String name;private Integer age;private String Sex;public String getName() {return name;}public void setName(String name) {this.name = name;}public Integer getAge() {return age;}public void setAge(Integer age) {this.age = age;}public String getSex() {return Sex;}public void setSex(String sex) {Sex = sex;}@Overridepublic String toString() {return "Person{" +"name='" + name + '\'' +", age='" + age + '\'' +", Sex='" + Sex + '\'' +'}';}
}public class JavaSerializableDemo {//初始化一个Person对象public static Person initPerson(){Person person = new Person();person.setAge(18);person.setName("Mike");person.setSex("man");return person;}
//序列化public static Person serialize(){Person person = initPerson();//创建一个文件输出流,把序列化的数据写到文件中。//创建一个ObjectOutputStream 用于序列化对象,将序列化的数据写到fileOutputStream 流中。try (FileOutputStream fileOutputStream = new FileOutputStream("person");ObjectOutputStream objectOutputStream = new ObjectOutputStream(fileOutputStream);){//序列化对象objectOutputStream.writeObject(person);} catch (IOException e) {e.printStackTrace();}return person;}
//反序列public static Person deSerialize(){Person person = null;//创建一个文件输入流,用于读取序列化文件。//创建一个objectInputStream ,用于反序列化对象,通过读取fileInputStream 流数据进行反序列化try (FileInputStream fileInputStream = new FileInputStream("person");ObjectInputStream objectInputStream = new ObjectInputStream(fileInputStream);){//反序列化person = (Person) objectInputStream.readObject();return person;}catch (IOException e){e.printStackTrace();} catch (ClassNotFoundException e) {e.printStackTrace();}return person;}public static void main(String[] args) {Person person = serialize();Person person1 = deSerialize();System.out.println("person==>" + person);System.out.println("person1==>" +person1);//判断两个对象是否是用一个对象System.out.println("person == person1 ==> " + (person == person1));//分别修改属性,看看会不会影响另外一个对象person.setAge(20);person1.setName("john");System.out.println("person==>" + person);System.out.println("person1==>" +person1);}
}

结果:

第一行和第二行对象的属性值一样说明了序列化会保存对象的状态信息。
第三行返回false,说明java的序列化机制实现的是对象的copy,而不是对象引用的copy。
第四行和第五行,修改对象属性不会影响另外一个对象,也说明了java的序列化机制实现的是对象的copy,而不是对象引用的copy。

如果被序列化的对象的类没有实现Serializable接口,就会抛出异常。

serialVersionUID

serialVersionUID,是序列化版本控制UID,其目的是序列化对象版本控制,有关各版本反序列化时是否兼容。如果在新版本中这个值修改了,新版本就不兼容旧版本,反序列化时会抛出InvalidClassException异常。如果修改较小,比如仅仅是增加了一个属性,我们希望向下兼容,老版本的数据都能保留,那就不用修改;如果我们删除了一个属性,或者更改了类的继承关系,必然不兼容旧数据,这时就应该手动更新版本号,即SerialVersionUid。

如果没有显示指定该版本号,编译器会在编译类文件时自动帮我们根据类的信息创建一个版本号。如果没有指定版本号的情况下修改类文件信息,会导致编译器生成的版本id不一样,在反序列化时就会抛出异常。

步骤:

  1. 不显示指定版本id。进行序列化操作。
  2. 修改Person类,比如新增一个fatherName属性。
  3. 然后用之前序列化的文件进行反序列化为修改后的Person类。

结果:

由于Person文件进过了修改,编译器生成的版本id就会变化。然后就会导致序列化时的版本id与反序列化时的版本id对不上而抛出异常。

指定版本id后,就算修改文件,也不会导致反序列化异常。

静态变量的序列化

静态变量不会被序列化,也就是序列化时会忽略静态变量。因为静态变量是类级别的属性,而java序列化机制保存的是对象的状态信息。

代码:
person新增一个live的静态属性。

public static void main(String[] args) {//先序列化Person person = serialize();//修改静态变量的值person.live = "宇宙";//反序列化Person person1 = deSerialize();System.out.println(person1.live);}

结论:
首先把对象序列化,如果会把静态变量的值序列化的话,那么反序列化出来的对象的静态变量值就应该是序列化前的值,也就是地球村。
如果会忽略静态变量的话,那么反序列化出来的对象的静态变量值就应该是序列化后修改的值,也就是宇宙。

transient

transient关键字用于修饰成员变量,被修饰的成员变量不会被java的序列化机制序列化。所以反序列化时得到的对象的该属性会使用默认值,对象引用默认null,int 默认为0等,或者如果该类里面初始化了,就使用初始化的值。

Person加上一个被transient修饰的属性aliasName

修改测试代码和初始化代码,初始化aliasName,序列化和反序列化代码不变:

//初始化一个Person对象public static Person initPerson(){Person person = new Person();person.setAge(18);person.setName("Mike");person.setSex("man");person.setAliasName("Handsome Mike");return person;}public static void main(String[] args) {//先序列化Person person = serialize();//反序列化Person person1 = deSerialize();//输出被transient修饰的属性aliasNameSystem.out.println(person.getAliasName());System.out.println(person1.getAliasName());}

结果:

反序列化的对象Person的aliasName属性为null。表名在序列化时,该属性被忽略了。

继承关系的序列化:

序列化某个类,如果该类有父类且父类没有实现Serializable接口,则不会序列化父类的属性,反序列化时父类的属性会使用默认值或者创建对象时就初始化的值。

//person类继承一个父类。
//实现Serializable接口
public class Person extends SupperPerson implements Serializable{private String name;private Integer age;private String Sex;private transient String aliasName;public static  String live = "地球村";public String getAliasName() {return aliasName;}public void setAliasName(String aliasName) {this.aliasName = aliasName;}public String getName() {return name;}public void setName(String name) {this.name = name;}public Integer getAge() {return age;}public void setAge(Integer age) {this.age = age;}public String getSex() {return Sex;}public void setSex(String sex) {Sex = sex;}@Overridepublic String toString() {return "Person{" +"name='" + name + '\'' +", age='" + age + '\'' +", Sex='" + Sex + '\'' +'}';}
}//父类
public class SupperPerson{private String aaa;private String bbb;public String getAaa() {return aaa;}public void setAaa(String aaa) {this.aaa = aaa;}public String getBbb() {return bbb;}public void setBbb(String bbb) {this.bbb = bbb;}
}//初始化:
//初始化一个Person对象public static Person initPerson(){Person person = new Person();person.setAge(18);person.setName("Mike");person.setSex("man");person.setAliasName("Handsome Mike");//初始化父类属性person.setAaa("aaaaa");person.setBbb("bbbbb");return person;}//测试
public static void main(String[] args) {//先序列化Person person = serialize();//反序列化Person person1 = deSerialize();//输出被transient修饰的属性aliasNameSystem.out.println(person.getAaa());System.out.println(person.getBbb());System.out.println(person1.getAaa());System.out.println(person1.getBbb());}

结果:

父类的属性aaa和bbb没有被序列化保存下来。

如果父类也实现了Serializable接口,就会序列化父类的属性。


其他代码一样。
结果:

反序列化后父类的属性不再是null。

通常序列化实现深度克隆

克隆一个对象就是保存原对象的状态信息新建一个对象,实现对象的拷贝,新对象与原对象在堆上是两个不同的对象。

public class Student implements Serializable,Cloneable{private String name;private Integer age;private String Sex;public String getName() {return name;}public void setName(String name) {this.name = name;}public Integer getAge() {return age;}public void setAge(Integer age) {this.age = age;}public String getSex() {return Sex;}public void setSex(String sex) {Sex = sex;}@Overridepublic String toString() {return "Student{" +"name='" + name + '\'' +", age=" + age +", Sex='" + Sex + '\'' +'}';}@Overrideprotected Object clone() throws CloneNotSupportedException {//实现父类的clone方法。调用序列化和反序列化。return deSerialize(serialize(this));}/*** 序列化返回序列化的字节数组* @param student* @return*/private  byte[] serialize(Student student){try (ByteArrayOutputStream bos = new ByteArrayOutputStream();ObjectOutputStream objectOutputStream = new ObjectOutputStream(bos);){objectOutputStream.writeObject(student);return bos.toByteArray();} catch (IOException e) {e.printStackTrace();}return null;}/*** 通过序列化字节数组反序列化成Student对象。* @param studentByteArray* @return*/private  Student deSerialize(byte[] studentByteArray){try (ByteArrayInputStream bis = new ByteArrayInputStream(studentByteArray);ObjectInputStream objectInputStream = new ObjectInputStream(bis);){Student student = (Student) objectInputStream.readObject();return student;}catch (IOException e){e.printStackTrace();} catch (ClassNotFoundException e) {e.printStackTrace();}return null;}
}//测试public static void main(String[] args) {Student student = new Student();student.setAge(22);student.setName("Mike");student.setSex("man");try {Student studentCopy = (Student) student.clone();System.out.println(student);System.out.println(studentCopy);System.out.println(student == studentCopy);System.out.println(student.getName() == studentCopy.getName());} catch (CloneNotSupportedException e) {e.printStackTrace();}}

结果:

第一行和第二行原对象与克隆对象的属性一致,表示克隆成功。
第三行比较的是原对象与克隆对象是否是同一个对象,比较对象的地址,返回false,表名是两个不同的对象。
第四行判断的是对象的属性是否也进行了克隆,比较两属性对象的地址,返回false,表示也不是同一个对象,达到了深克隆。

总结:

  1. 在java中,只要一个类实现了java.io.Serializable接口,那么他就可以被序列化。
  2. 通过ObjectInputStream和ObjectOutputStream对对象进行序列化和反序列化。
  3. 对象能否被反序列化,不仅取决于对象代码是否一直不变,还取决于UID。
  4. 序列化不保存静态属性。
  5. 要想父类的属性也进行序列化,父类也要实现java.io.Serializable接口。
  6. transient关键字控制属性是否会被序列化,如果没有被序列化的属性反序列化后,会被设置成初始值。
  7. 通过序列化机制可以实现对象深拷贝。

一篇文章搞定java序列化机制相关推荐

  1. 一篇文章搞定java中的垃圾回收机制面试题

    一篇文章搞定java中的垃圾回收机制面试题 任何语言在运行过程中都会创建对象,也就意味着需要在内存中为这些对象在内存中分配空间,在这些对象失去使用的意义的时候,需要释放掉这些内容,保证内存能够提供给新 ...

  2. 你了解java序列化吗?一篇文章搞懂java序列化

    程序员的成长之路 互联网/程序员/技术/资料共享 关注 阅读本文大概需要 9 分钟. 来自:https://www.cnblogs.com/xiaohuiduan/p/12398775.html 首先 ...

  3. 一篇文章搞定Java处理Excel表格的各种疑难杂症

    文章目录 简介 结构 Maven Gradle 扩展组件知识 快速体验 创建Excel 读取Excel 开发指南 读取Excel方法对比 读取Excel的所有Sheet 方式一 方式二 读取单元格的值 ...

  4. 一篇文章搞定《RecyclerView缓存复用机制》

    一篇文章搞定<RecyclerView缓存复用机制> 前言 零.为什么要缓存 一.RecyclerView如何构建我们的列表视图 二.缓存过程 三.缓存结构 1.mChangedScrap ...

  5. 超硬核!!!一篇文章搞定TCP、UDP、Socket、HTTP(详细网络编程内容+现实解释三次握手四次挥手+代码示例)【网络编程 1】

    TCP.UDP.Socket 一天面试的经验: 什么是网络编程 网络编程中两个主要的问题 网络协议是什么 为什么要对网络协议分层 计算机网络体系结构 1 TCP / UDP 1.1 什么是TCP/IP ...

  6. 一篇文章搞定《Android布局优化》

    ------<一篇文章搞定Android布局优化> 前言 为什么要进行布局优化? Android绘制原理 双缓冲机制 布局加载原理 布局加载优化的一些方法介绍 AsyncLayoutInf ...

  7. Android NDK开发之旅(2):一篇文章搞定Android Studio中使用CMake进行NDK/JNI开发

    Android NDK开发之旅(2):一篇文章搞定android Studio中使用CMake进行NDK/JNI开发 (码字不易,转载请声明出处:http://blog.csdn.NET/andrex ...

  8. 一篇文章搞定GVIM(根据工作经验持续更新)

    文章目录 0.引言 1.在Linux下面安装VIM 2.基本操作 2.1三种模式 2.1 保存退出:wq没反应?! 2.2 解决鼠标不能用的问题 2.3 VIM上下左右移动hjkl 2.4 跳转到第n ...

  9. 一篇文章搞定百度OCR图片文字识别API

    一篇文章搞定百度OCR图片文字识别API https://www.jianshu.com/p/7905d3b12104 转载于:https://www.cnblogs.com/chongdongxia ...

最新文章

  1. 大佬原创 | 深度学习60讲453页pdf下载
  2. 28-Interview-面试
  3. tomcat安装部署
  4. 【FLASH BUILDER 4.6 快捷键】只记几个对自己有帮助的
  5. BrowserSync开发利器
  6. Windows 系统中 Python下 Pygame 的安装
  7. 如何生成全局唯一标识
  8. PullToRefresh
  9. wiki源码_一个轻量级的企业Wiki和团队知识分享平台:MM-Wiki搭建教程
  10. IdentityServer4支持的授权类型以及组合
  11. cocos2d-x3.0 关于CCAnimate 的一些资料
  12. 内存条hyperx_一键开启内存条最高效能 HyperX雷电系列3733MHz内存条评测
  13. 【推荐架构day4】微博推荐引擎的基本算法:核心细节
  14. Java:spring Value注解用法详解
  15. 制作手札---RPG是怎样做成的 (三)
  16. Nature子刊 定制饮食去除半胱氨酸和蛋氨酸可诱导细胞自毁进而治疗脑瘤?
  17. poi-tl实现word文档按模板下载
  18. Python基础学习——Numpy包(2、索引、切片与迭代)
  19. nefuoj 9 喜洋洋
  20. 一念起,天涯咫尺;一念灭,咫尺天涯

热门文章

  1. Python学习笔记:序列之字符串
  2. 【BZOJ1037】【codevs1410】生日聚会,DP
  3. python中代码块使用缩进来表示对吗_Python 为什么使用缩进来划分代码块?
  4. 2017.4.22 hankson的趣味题 思考记录
  5. c语言统计字母数字符号个数程序,请问这个用c怎么做:输入一串字符,分别统计其中数字和字母的个数...
  6. 【Python】Python3.7.3 - memoization 结果缓存记忆程序设计优化技术
  7. sqlserver 指定的网络名不再可用_50个比较实用的SQL Server查询语句(1)
  8. 火车票售票系统mysql_今日开售!2020元旦火车票可以买了,春运首日车票也快了→...
  9. ae制h5文字动画_AE文字拉伸动画如何制作
  10. Observe rainy world