李林超博客
首页
归档
留言
友链
动态
关于
归档
留言
友链
动态
关于
首页
Java
正文
Java序列化和反序列化
Leefs
2019-12-28 PM
4165℃
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
Java
326
大数据
229
工具
31
其它
25
GO
47
NLP
4
标签云
线程池
Azkaban
锁
Hbase
Livy
Thymeleaf
Java编程思想
Map
Stream流
FileBeat
Flume
nginx
Eclipse
LeetCode刷题
哈希表
Zookeeper
Http
工具
Java
Elasticsearch
Redis
查找
容器深入研究
前端
NIO
Netty
Git
Hive
Golang
二叉树
友情链接
申请
范明明
庄严博客
Mx
陶小桃Blog
虫洞
评论已关闭