李林超博客
首页
归档
留言
友链
动态
关于
归档
留言
友链
动态
关于
首页
Java
正文
Java序列化和反序列化
Leefs
2019-12-28 PM
3035℃
2条
# Java序列化和反序列化 ### 一、概念 在使用一个东西之前首先要知道这个东西是干啥用的,所以,不管是枯燥,还是无聊,还得先说概念。。。 #### 1. 序列化和反序列化: **序列化:**把对象转换为字节序列的过程称为对象的序列化。 **反序列化:**把字节序列恢复为对象的过程称为对象的反序列化。 #### 2. 用途: + 1.把对象的字节序列永久地保存到硬盘上,通常存放在一个文件中;(持久化对象) + 2.在网络上传送对象的字节序列。(网络传输对象) Java序列化和反序列化的这两种用途在开发中也是很常见的,因为它可以实实在在的解决工作中的一些问题。 **问题1:**假如我们现在需要作一个PC端的闯关类小游戏,当用户没有一次性过完全部关卡,下次在登录又要从第一关重新开始,这样的体验是不是很不好。 这时候需要在本地的某个目录下生成一个文件,将该用户的通关状态和基本信息都存入该文件当中,等用户再次登录时读取该文件中的信息,在界面展示用户的上次登录状态。 **问题2:**当项目实体类是String类型,但是接口文档中规定前端接收的是一个int类型,这时候我们又该怎么办呢? 选择改实体类或者是改接口文档?选择上面两种的任意一种,放心吧,都会有人拿刀来追着你砍(小编不推荐上方两个的任何一个,出了问题概不负责) #### 3. 使用 在Java中,如果一个对象要想实现序列化,必须要实现下面两个接口之一: + `Serializable` 接口 + `Externalizable` 接口 **`Serializable`接口:** 一个对象想要被序列化,那么它的类就要实现此接口或者它的子接口。 这个对象的所有属性(包括private属性、包括其引用的对象)都可以被序列化和反序列化来保存、传递。不想序列化的字段可以使用transient修饰。 由于Serializable对象完全以它存储的二进制位为基础来构造,因此并不会调用任何构造函数,因此Serializable类无需默认的构造函数,但是当Serialization类的父类没有实现Serializable接口时,反序列化过程会调用父类的默认构造函数,因此该父类必需有默认构造函数,否则会抛出异常。 使用transient关键字阻止序列化虽然简单方便,但被它修饰的属性被完全隔离在序列化机制之外,导致了在反序列化时无法获取该属性的值,而通过在需要序列化的对象的Java类里加入writeObject()方法与readObject()方法可以控制如何序列化各属性,甚至完全不序列化某些属性或者加密序列化某些属性。 **Externalizable接口:** 它是Serializable接口的子类,用户要实现的writeExternal()和readExternal() 方法,用来决定如何序列化和反序列化。 因为序列化和反序列化方法需要自己实现,因此可以指定序列化哪些属性,而transient在这里无效。 对Externalizable对象反序列化时,会先调用类的无参构造方法,这是有别于默认反序列方式的。如果把类的不带参数的构造方法删除,或者把该构造方法的访问权限设置为private、默认或protected级别,会抛出java.io.InvalidException: no valid constructor异常,因此Externalizable对象必须有默认构造函数,而且必需是public的。 **对比:** 使用时,你只想隐藏一个属性,比如用户对象user的密码pwd,如果使用Externalizable,并除了pwd之外的每个属性都写在writeExternal()方法里,这样显得麻烦,可以使用Serializable接口,并在要隐藏的属性pwd前面加上transient就可以实现了。如果要定义很多的特殊处理,就可以使用Externalizable。 当然这里我们有一些疑惑,Serializable 中的writeObject()方法与readObject()方法科可以实现自定义序列化,而Externalizable 中的writeExternal()和readExternal() 方法也可以,他们有什么异同呢? + readExternal(),writeExternal()两个方法,这两个方法除了方法签名和readObject(),writeObject()两个方法的方法签名不同之外,其方法体完全一样。 + 需要指出的是,当使用Externalizable机制反序列化该对象时,程序会使用public的无参构造器创建实例,然后才执行readExternal()方法进行反序列化,因此实现Externalizable的序列化类必须提供public的无参构造。 + 虽然实现Externalizable接口能带来一定的性能提升,但由于实现ExternaLizable接口导致了编程复杂度的增加,所以大部分时候都是采用实现Serializable接口方式来实现序列化。 #### 4. **序列化版本号** 在序列化过程中,可以控制序列化的版本。该字段为被序列化对象中的serialVersionUID字段。 ```java public class User implements Serializable{ public final static long serialVersionUID = 1L; } ``` 一个对象数据,在反序列化过程中,如果序列化串中的serialVersionUID与当前对象值不同,则反序列化失败,否则成功。 如果serialVersionUID没有显式生成,系统就会自动生成一个。生成的输入有:类名、类及其属性修饰符、接口及接口顺序、属性、静态初始化、构造器。任何一项的改变都会导致serialVersionUID变化。 属性的变化都会导致自动生成的serialVersionUID发生变化。例如,对于对象A,我们生成序列化的S(A),然后修改A的属性,则此时A的serialVersionUID发生变化。反序列化时,S(A)与A的serialVersionUID不同,无法反序列化。会报序列号版本不一致的错误。 为了避免这种问题, 一般系统都会要求实现serialiable接口的类显式的生明一个serialVersionUID。显式定义serialVersionUID的两种用途: + 希望类的不同版本对序列化兼容时,需要确保类的不同版本具有相同的serialVersionUID; + 不希望类的不同版本对序列化兼容时,需要确保类的不同版本具有不同的serialVersionUID。 如果我们保持了serialVersionUID的一致,则在反序列化时,对于新增的字段会填入默认值null(int的默认值0),对于减少的字段则直接忽略。 ### 二、使用示例 本示例,进行将对象的字节序列永久地保存到硬盘上,并通过反序列化从硬盘中读取到对象的信息。 **实体类** ```java public class User implements Serializable { private int id; private String username; private String password; private double money; private String msg; ``` 省略了get和set方法、toString()方法、有参和无参构造方法。 **为对象创建数据** ```java public class Test_User { public static void main(String[] args) throws IOException { User u = new User(); u.setId(12033); u.setUsername("李林超博客111"); u.setPassword("009900222"); u.setMoney(23.44); u.setMsg("你以阅读十分钟,可以做一个短暂休息"); User u2 =new User(12034,"李林超博客222","009900222",23.44,"你以阅读十分钟,可以做一个短暂休息"); User u3 =new User(12035,"李林超博客333","009900222",23.44,"你以阅读十分钟,可以做一个短暂休息"); User u4 =new User(12036,"李林超博客555","009900222",23.44,"你以阅读十分钟,可以做一个短暂休息"); User u5 =new User(12037,"李林超博客444","009900222",23.44,"你以阅读十分钟,可以做一个短暂休息"); //将对象写出到硬盘上的某个文件里 ObjectOutputStream oos = new ObjectOutputStream(new FileOutputStream("F:/user.bat"));//将数据保存到 f:/user.bat文件中 oos.writeObject(u); oos.writeObject(u2); oos.writeObject(u3); oos.writeObject(u4); oos.writeObject(u5); oos.flush(); oos.close(); System.out.println("谢谢光顾,我们下次再见"); } } ``` **从磁盘中读取文件对象** ```java public class Test_User02 { public static void main(String[] args) throws IOException { HashMap
map = new HashMap(); //将磁盘某个文件里的对象读取到程序里 ObjectInputStream ois = new ObjectInputStream(new FileInputStream("F:/user.bat")); while(true){ try{ User u = (User) ois.readObject(); map.put(u.getId(),u); } catch (Exception e) { break; } } Scanner sc = new Scanner(System.in); System.out.println("请输入您的编号"); int id = sc.nextInt(); User u = map.get(id); if(u != null){ System.out.println(u); }else { System.out.println("你的编号有问题"); } } } ``` `ObjectOutputStream`和`ObjectInputStream`方法: > 1. 1.对象操作流 > > 该流可以将一个对象写出,或者获取一个对象到程序中,也就是执行了序列化和反序列化操作。 > > 2. 2.使用方法 > > 前提:需要被序列化和反序列化的类必须实现`Serializable`接口。 *附:[参考文章链接](https://blog.csdn.net/guohao_1/article/details/92587425)*
标签:
Java
,
JavaSE
,
序列化和反序列化
非特殊说明,本博所有文章均为博主原创。
如若转载,请注明出处:
https://lilinchao.com/archives/342.html
上一篇
正则表达式--split()方法
下一篇
正则表达式--替换操作和Rest()方法
取消回复
评论啦~
提交评论
已有 2 条评论
茂林
嘀嘀嘀
小火车来啦
回复
2019-12-30 08:16
Leefs
博主
@茂林
哈哈,准时打卡
回复
2019-12-30 09:06
栏目分类
随笔
2
Java
326
大数据
229
工具
31
其它
25
GO
47
标签云
HDFS
MyBatisX
Spark Core
LeetCode刷题
Spring
Shiro
FastDFS
RSA加解密
Eclipse
队列
Python
Java工具类
Elastisearch
MyBatis-Plus
JVM
Jenkins
Azkaban
FileBeat
Yarn
Thymeleaf
Hadoop
Git
排序
数据结构和算法
Java
锁
Scala
Stream流
CentOS
正则表达式
友情链接
申请
范明明
庄严博客
Mx
陶小桃Blog
虫洞
嘀嘀嘀
小火车来啦
哈哈,准时打卡