序列化和反序列化
- 序列化和反序列化
- 作用
- 为什么需要
- 用途
- Serializable
- 使用
- serialVersionUID
- 不设置的后果
- 什么时候修改
- Externalizable
- 序列化的顺序
- json序列化
序列化和反序列化
序列化
:把对象转换为字节序列的过程称为对象的序列化。
反序列化
:把字节序列恢复为对象的过程称为对象的反序列化。
其实就是将代码中运行的对象从内存中保存为字节序列可以存储和传输。
作用
- 把对象的字节序列永久地保存到硬盘上(通常存放在一个文件中);
- 在网络上传送对象的字节序列。
为什么需要
Java对象是运行在JVM的堆内存中的,如果JVM停止后,它的生命也就戛然而止。
如果想在JVM停止后,把这些对象如何保存起来呢。我们只能保存到硬盘上或者传输到其他地方了,但是很多硬件或者说传输只支持二进制而不认识java的对象,所以需要将这些对象转换成字节序列了。
我感觉和高级语言编译成二进制差不多。java对象是我们好理解的,二进制是机器理解的。
用途
- 可以做对象的备份会恢复
- 兼容性规定
- 数据存储,保存到数据库、文件等
- 传输
怎么样序列化需要我们来规定,所以学习这些规定的方法
如果不规定则会报错
MyArrayList 是自己实现的一个动态数组的类,还未实现Serializable接口
public void testSerializable() {
MyArrayList list = new MyArrayList();
for (int i = 0; i < 100; i++) {
list.add(i);
}
try(
ObjectOutputStream o = new ObjectOutputStream(new FileOutputStream("a.txt"));
ObjectInputStream in = new ObjectInputStream(new FileInputStream("a.txt"));
) {
o.writeObject(list);
MyArrayList l = (MyArrayList) in.readObject();
System.out.println(l);
} catch (IOException | ClassNotFoundException e) {
throw new RuntimeException(e);
}
}
会出现这样的错误
Serializable
Serializable接口是一个标记接口,没有方法或字段。一旦实现了此接口,就标志该类的对象就是可序列化的。
这种是隐式序列化(不需要手动),这种是最简单的序列化方式,会自动序列化所有非static和 transient关键字修饰的成员变量
使用
实现之后在运行上面代码
a.txt,看不懂的
serialVersionUID
意思就是序列化版本号ID,其实每一个实现Serializable接口的类,都有一个表示序列化版本标识符的静态变量,或者默认等于1L,或者等于对象的哈希码。
JAVA序列化的机制是通过判断类的serialVersionUID来验证版本是否一致的。在进行反序列化时,会把serialVersionUID和本地相应实体类的serialVersionUID进行比较,如果相同,反序列化成功,如果不相同,就抛出InvalidClassException异常。
修改一下在读取,记得注释掉序列化的代码
不设置的后果
在类中新增一个不使用的字段
然后序列化后删除这个字段
我们会发现其报错
Caused by: java.io.InvalidClassException:
com.yu.MyArrayList; local class incompatible:
stream classdesc serialVersionUID = 8278873907900787181,
local class serialVersionUID = -9196603521067733754
我们不是没有设置这个字段吗,怎么报错会显示这个呢。这个是怎么来的
在没有定义 serialVersionUID的时候,就会生成默认的序列化唯一标示。
他的生成规则是根据类名,方法和属性等参数生成的 hash 值
所以在删除后其hash值会改变。
但是这个字段对于我们来说毫无影响,所以我们可以自行设置serialVersionUID 的值
在进行上面操作且不修改serialVersionUID 的值,会发现代码通过了。
什么时候修改
在阿里巴巴开发手册中有
不是说一定不能修改,在不兼容的时候在修改。
就像上面的这个字段对代码毫无影响,那么我们就不需要修改了。
这个应该强制一下实现了这个接口的就必须赋初值,不然随便修改一下属性就不能反序列化了。
这就是差不多是一个兼容性问题,就像各个版本的JDK代码都拿出来看一下,那些向下兼容的类的serialVersionUID是没有变化过的。
Externalizable
Externalizable继承了Serializable接口,还定义了两个抽象方法:writeExternal()和readExternal(),如果开发人员使用Externalizable来实现序列化和反序列化,需要重写writeExternal()和readExternal()方法
这2个方法给定了流,直接调用流的方法自定义读和写就行
如我什么都不序列化,获取里面的大小就是0
只序列化大小
如果我序列化大小和容量,打印数据就会报错。
因为我们的类中的数组没有这么大初始为空,所以会报错。
序列化的顺序
那么我们如果重复序列化size会发生什么
获取其中的size和capacity
我们来混乱的定义
打印结果
可以看到顺序和我们序列化的顺序一样和属性无关
最后我们用不同属性进行
发现了几点规律
接收和发送顺序完全一样,不会是int读int,object读object。
顺序错了会报错
不同类型混合接收
至于为什么是这样,应该涉及太底层的,我还不知道,到时候看流的底层的时候在看看。
json序列化
json使用就比较简单
但是会报错。
这里看到只序列化了size。
因为fastjson会扫描其中的getter方法,我们只设置了getSize方法。
所以其他的访问不到。
json的具体内容以后在说,这里只介绍其普通使用。