Java对象的序列化和反序列化源码阅读
前言
序列化和反序列化看起来用的不多,但用起来就很关键,因为稍一不注意就会出现问题。序列化的应用场景在哪里?当然是数据存储和传输。比如缓存,需要将对象复刻到硬盘存储,即使断电也可以重新反序列化恢复。下面简单理解序列化的用法以及注意事项。
如何序列化
Java中想要序列化一个对象,必须实现Serializable
接口。然后就可以持久化和反序列化了。下面是一个简单用法。
我们给一个测试类:
package com.test.java.serial;
import lombok.Builder;
import lombok.Data;
import java.io.Serializable;
/**
* @author Ryan Miao
*/
@Data
@Builder
public class Foo implements Serializable {
private static final String LOGGER = "logger";
public static final String PUB_STATIC_FINAL = "publicStaticFinal";
public static String PUB_STATIC;
public String fa;
private String fb;
transient public String ta;
transient private String tb;
}
然后,测试序列化和反序列的数据是否丢失。
public class TestSerialize {
private static final String filename = "D:/test.txt";
@Test
public void testSer() throws IOException, ClassNotFoundException {
final Foo foo = Foo.builder()
.fa("fa")
.fb("fb")
.ta("ta")
.tb("tb")
.build();
Foo.PUB_STATIC = "test";
ObjectOutputStream os = new ObjectOutputStream(
new FileOutputStream(filename));
os.writeObject(foo);
os.flush();
os.close();
}
@Test
public void testRead() throws IOException, ClassNotFoundException {
ObjectInputStream is = new ObjectInputStream(new FileInputStream(filename));
Foo foo2 = (Foo) is.readObject();
is.close();
Assert.assertEquals("fa", foo2.getFa());
Assert.assertEquals("fb", foo2.getFb());
Assert.assertEquals(null, foo2.getTa());
Assert.assertEquals(null, foo2.getTb());
Assert.assertNull(foo2.PUB_STATIC);
}
}
显然,transient
修饰的字段不能被序列化,至于静态字段,这里不做测试,但要清楚。静态字段只和class类相关,和实例无关。而序列化是针对实例的,所以无所谓对比内容变化。那么,静态字段反序列化后数据是什么样子的呢?当然是类变量本身应该的样子。如果没有初始化,则是默认值, 本测试中的结果为null。
为什么可以序列化
我们只要实现了Serialiable
就可以序列化,那么为什么呢?查看ObjectOutputStream
的writeObject
方法。
// remaining cases
if (obj instanceof String) {
writeString((String) obj, unshared);
} else if (cl.isArray()) {
writeArray(obj, desc, unshared);
} else if (obj instanceof Enum) {
writeEnum((Enum<?>) obj, desc, unshared);
} else if (obj instanceof Serializable) {
writeOrdinaryObject(obj, desc, unshared);
} else {
if (extendedDebugInfo) {
throw new NotSerializableException(
cl.getName() + "\n" + debugInfoStack.toString());
} else {
throw new NotSerializableException(cl.getName());
}
}
显然,只针对String,Enum以及Serializable做了处理,因此想要序列化必须要实现这个接口。当然,String和Enum也实现了Serializable。
如何自定义序列化,Java基础类库中的ArrayList等为什么用transient还能序列化
简单的对象,对于不想序列化的字段,只要声明为transient
就好。而有时候,我想对部分字段处理后序列化。比如ArrayList中存储数据的transient Object[] elementData;
。我们知道ArrayList是可以序列化的,根源就在于自定义这里了。下面跟踪ObjectOutputStream
源码,知道自定义的执行部分就可以验证了。
入口: java.io.ObjectOutputStream#writeObject
public final void writeObject(Object obj) throws IOException {
if (enableOverride) {
writeObjectOverride(obj);
return;
}
try {
writeObject0(obj, false);
} catch (IOException ex) {
if (depth == 0) {
writeFatalException(ex);
}
throw ex;
}
}
然后,核心方法
private void writeObject0(Object obj, boolean unshared)
throws IOException{
boolean oldMode = bout.setBlockDataMode(false);depth++;
try {
//省略若干行
for (;;) {
// 省略若干行
desc = ObjectStreamClass.lookup(cl, true);
//省略若干行
}
//省略若干行
if (obj instanceof String) {
writeString((String) obj, unshared);
} else if (cl.isArray()) {
writeArray(obj, desc, unshared);
} else if (obj instanceof Enum) {
writeEnum((Enum<?>) obj, desc, unshared);
} else if (obj instanceof Serializable) {
writeOrdinaryObject(obj, desc, unshared);
} else {
//....
}
} finally {
depth--;
bout.setBlockDataMode(oldMode);
}
}
这里,显然可以看到真正的执行序列化代码是writeOrdinaryObject(obj, desc, unshared);
。 但直接追踪进去发现里面有许多初始化的字段是在之前做的处理。因此,先卖个关子,看前面初始化的部分,只找到我们想要初始化的字段即可。
进入desc = ObjectStreamClass.lookup(cl, true);
static ObjectStreamClass lookup(Class<?> cl, boolean all) {
//省略若干行
if (entry == null) {
try {
entry = new ObjectStreamClass(cl);
} catch (Throwable th) {
entry = th;
}
//.....
}
//省略若干行
}
进入entry = new ObjectStreamClass(cl);
这里就是真正的初始化地方,前面省略的代码是缓存处理,当然缓存使用的ConcurrentHashMap。
private ObjectStreamClass(final Class<?> cl) {
//省略无数行以及括号
writeObjectMethod = getPrivateMethod(cl, "writeObject",
new Class<?>[] { ObjectOutputStream.class },
Void.TYPE);
readObjectMethod = getPrivateMethod(cl, "readObject",
new Class<?>[] { ObjectInputStream.class },
Void.TYPE);
//省略无数行
没错,费了这么大劲就是为了找到这两个method。通过反射,获取到目标class的两个私有方法writeObject
, readObject
。这两个就是自定义方法所在。
初始化完毕之后,我们再来继续序列化的代码. 回到刚才的核心方法,找到writeOrdinaryObject(obj, desc, unshared);
, 进入,然后,继续找到writeSerialData(obj, desc);
, 到这里就是真正执行序列化的代码了。
private void writeSerialData(Object obj, ObjectStreamClass desc)
throws IOException
{
ObjectStreamClass.ClassDataSlot[] slots = desc.getClassDataLayout();
for (int i = 0; i < slots.length; i++) {
ObjectStreamClass slotDesc = slots[i].desc;
if (slotDesc.hasWriteObjectMethod()) {
//....
try {
curContext = new SerialCallbackContext(obj, slotDesc);
bout.setBlockDataMode(true);
slotDesc.invokeWriteObject(obj, this);
bout.setBlockDataMode(false);
bout.writeByte(TC_ENDBLOCKDATA);
} finally {
//...
}
curPut = oldPut;
} else {
defaultWriteFields(obj, slotDesc);
}
}
}
显然,判断writeObject
这个method是否初始化了,如果有,则直接调用这个方法,没有则默认处理。到此,跟踪完毕,我想要自定义序列化只要重写writeObject
, readObject
这两个方法即可。
下面看看ArrayList是怎么做的
private void writeObject(java.io.ObjectOutputStream s)
throws java.io.IOException{
// Write out element count, and any hidden stuff
int expectedModCount = modCount;
s.defaultWriteObject();
// Write out size as capacity for behavioural compatibility with clone()
s.writeInt(size);
// Write out all elements in the proper order.
for (int i=0; i<size; i++) {
s.writeObject(elementData[i]);
}
if (modCount != expectedModCount) {
throw new ConcurrentModificationException();
}
}
因为数组被设置不允许序列化,先默认序列化其他信息,然后单独处理数组里的内容,挨着写入元素。然后,对应读取方法也要改。
private void readObject(java.io.ObjectInputStream s)
throws java.io.IOException, ClassNotFoundException {
elementData = EMPTY_ELEMENTDATA;
// Read in size, and any hidden stuff
s.defaultReadObject();
// Read in capacity
s.readInt(); // ignored
if (size > 0) {
// be like clone(), allocate array based upon size not capacity
ensureCapacityInternal(size);
Object[] a = elementData;
// Read in all elements in the proper order.
for (int i=0; i<size; i++) {
a[i] = s.readObject();
}
}
}
为什么要这么做?因为数组元素有很多空余空间,对我们来说不需要序列化。通过这样自定义,把需要的元素序列化,可以节省空间。
serialVersionUID为什么有的有,有的没有,什么时候用,意义是什么
以下内容来自: https://www.cnblogs.com/ouym/p/6654798.html
什么是serialVersionUID ?
serialVersionUID表示:“串行化版本统一标识符”(serial version universal identifier),简称UID
serialVersionUID必须定义成下面这种形式:static final long serialVersionUID = xxxL;
serialVersionUID 用来表明类的不同版本间的兼容性。有两种生成方式: 一个是默认的1L;另一种是根据类名、接口名、成员方法及属性等来生成一个64位的哈希字段 。
为什么要声明serialVersionUID
java.io.ObjectOutputStream代表对象输出流,它的writeObject(Object obj)方法可对参数指定的obj对象进行序列化,把得到的字节序列写到一个目标输出流中。 java.io.ObjectInputStream代表对象输入流,它的readObject()方法从一个源输入流中读取字节序列,再把它们反序列化为一个对象,并将其返回。
只有实现了Serializable或Externalizable接口的类的对象才能被序列化。
Externalizable接口继承自Serializable接口,实现Externalizable接口的类完全由自身来控制序列化的行为,而仅实现Serializable接口的类可以采用默认的序列化方式 。 凡是实现Serializable接口的类都有一个表示序列化版本标识符的静态变量:private static final long serialVersionUID;
类的serialVersionUID的默认值完全依赖于Java编译器的实现,对于同一个类,用不同的Java编译器编译,有可能会导致不同的serialVersionUID。显式地定义serialVersionUID有两种用途:
- 在某些场合,希望类的不同版本对序列化兼容,因此需要确保类的不同版本具有相同的serialVersionUID;在某些场合,不希望类的不同版本对序列化兼容,
因此需要确保类的不同版本具有不同的serialVersionUID。 - 当你序列化了一个类实例后,希望更改一个字段或添加一个字段,不设置serialVersionUID,所做的任何更改都将导致无法反序化旧有实例,并在反序列化时抛出一个异常。
如果你添加了serialVersionUID,在反序列旧有实例时,新添加或更改的字段值将设为初始化值(对象为null,基本类型为相应的初始默认值),字段被删除将不设置。
注意事项
序列化时,只对对象的状态进行保存,而不管对象的方法;
当一个父类实现序列化,子类自动实现序列化,不需要显式实现Serializable接口;
当一个对象的实例变量引用其他对象,序列化该对象时也把引用对象进行序列化;
并非所有的对象都可以序列化,,至于为什么不可以,有很多原因了,比如:
- 安全方面的原因,比如一个对象拥有private,public等field,对于一个要传输的对象,比如写到文件,或者进行rmi传输等等,在序列化进行传输的过程中,这个对象的private等域是不受保护的。
- 资源分配方面的原因,比如socket,thread类,如果可以序列化,进行传输或者保存,也无法对他们进行重新的资源分 配,而且,也是没有必要这样实现。
参考
- 对Java Serializable(序列化)的理解和总结
- serialVersionUID的作用
- Java transient关键字使用小记
- ArrayList中elementData为什么被transient修饰?
Java对象的序列化和反序列化源码阅读的更多相关文章
- Java对象的序列化和反序列化[转]
Java基础学习总结--Java对象的序列化和反序列化 一.序列化和反序列化的概念 把对象转换为字节序列的过程称为对象的序列化.把字节序列恢复为对象的过程称为对象的反序列化. 对象的序列化主要有两种用 ...
- Java对象的序列化与反序列化
序列化与反序列化 序列化 (Serialization)是将对象的状态信息转换为可以存储或传输的形式的过程.一般将一个对象存储至一个储存媒介,例如档案或是记亿体缓冲等.在网络传输过程中,可以是字节或是 ...
- Java对象的序列化与反序列化-Json篇
说到Java对象的序列化与反序列化,我们首先想到的应该是Java的Serializable接口,这玩意在两个系统之间的DTO对象里面可能会用到,用于系统之间的数据传输.或者在RPC(远程方法调用)时可 ...
- 一文带你全面了解java对象的序列化和反序列化
摘要:这篇文章主要给大家介绍了关于java中对象的序列化与反序列化的相关内容,文中通过详细示例代码介绍,希望能对大家有所帮助. 本文分享自华为云社区<java中什么是序列化和反序列化?>, ...
- Java基础学习总结——Java对象的序列化和反序列化
一.序列化和反序列化的概念 把对象转换为字节序列的过程称为对象的序列化. 把字节序列恢复为对象的过程称为对象的反序列化. 对象的序列化主要有两种用途: 1) 把对象的字节序列永久地保存到硬盘上,通常存 ...
- java对象的序列化与反序列化使用
1.Java序列化与反序列化 Java序列化是指把Java对象转换为字节序列的过程:而Java反序列化是指把字节序列恢复为Java对象的过程. 2.为什么需要序列化与反序列化 我们知道,当两个进程进 ...
- Java基础学习总结--Java对象的序列化和反序列化
一.序列化和反序列化的概念 把对象转换成字节序列的过程称之为对象的序列化 把字节序列恢复为对象的过程称之为对象的反序列化 对象序列化的主要用途: 1)把对象的字节序列永久的保存到硬盘上,通常放在一个文 ...
- 深入理解Java对象的序列化与反序列化的应用
当两个进程在进行远程通信时,彼此可以发送各种类型的数据.无论是何种类型的数据,都会以二进制序列的形式在网络上传送.发送方需要把这个Java对象转换为字节序列,才能在网络上传送:接收方则需要把字节序列再 ...
- java 对象的序列化与反序列化
一.序列化和反序列化的概念 把对象转换为字节序列的过程称为对象的序列化. 把字节序列恢复为对象的过程称为对象的反序列化. 对象的序列化主要有两种用途: 1) 把对象的字节序列永久地保存到硬盘上,通常存 ...
随机推荐
- 20172319 2018.10.12《Java程序设计教程》第6周课堂实践(补写博客)
20172319 2018.10.12 <Java程序设计教程>第6周课堂测验 课程:<程序设计与数据结构> 班级:1723 学生:唐才铭 学号:20172319 指导老师:王 ...
- linux soname
在linux下使用动态库时,经常会发现明明编译时指定的是libA.so,可是程序运行时或通过ldd查看依赖却是libA.so.XXX, 原因跟linux下so库的soname有关,查看so库的sona ...
- Mac应用
App Store 安装: AnappyApp: 截图软件 Snap: Dock快捷键启动 izip Unarchiver: rar解压 Dr.Cleaner:内存清理.资源监控 下载安装: C ...
- CentOS 7使用通过二进制包安装MySQL 5.7.18
安装依赖 yum install -y libaio 下载 wget https://dev.mysql.com/get/Downloads/MySQL-5.7/mysql-5.7.18-linux- ...
- [原创]SpotLight性能监控工具使用介绍
[原创]SpotLight性能监控工具使用介绍 1 Spotlight工具是什么? SpotLight 是由Quest公司出品的一款第三方性能监控的图形化工具.SpotLight有一些的产品诸如可以 ...
- .net core中的高效动态内存管理方案
.net core在新增的System.Buffers中引入了一大堆高效内存管理的类,如span和memory.内存池.本文今天这里介绍一个高效动态内存访问方案. ReadOnlySequenceSe ...
- STM32的PWM输入模式设置并用DMA接收数据
参考 :STM32输入捕获模式设置并用DMA接收数据 PWM input mode This mode is a particular case of input capture mode. The ...
- STM32F4: Generating parallel signals with the FSMC
STM32F4: Generating parallel signals with the FSMC The goal: The memory controller can be used to ge ...
- 微信破解,解密?How To Decrypt WeChat EnMicroMsg.db Database?
20元现金领取地址:http://jdb.jiudingcapital.com/phone.html内部邀请码:C8E245J (不写邀请码,没有现金送) 国内私募机构九鼎控股打造,九鼎投资是在全国股 ...
- 手机也需“绿色环保”,省电类APP或将成为“标配”?
随着移动APP的大幅添加.非常多用户发现,这手机耗电量是越来越大了,在各种娱乐应用.办公应用.社交应用的冲击下,以往两天充一次电都OK.如今一天充一次还不够用,因为续航能力变弱.这也为用户带来 ...