每日一博 - Java序列化一二事儿
Posted 小小工匠
tags:
篇首语:本文由小常识网(cha138.com)小编为大家整理,主要介绍了每日一博 - Java序列化一二事儿相关的知识,希望对你有一定的参考价值。
文章目录
what
把Java对象转换为字节序列的过程,-----------> 序列化
把字节序列恢复为Java对象的过程,-----------> 反序列化
Why
我们知道,Java对象是运行在JVM的堆内存中的,如果JVM停止后,对象也就不复存在了。
如果想在JVM停止后,把这些对象保存到磁盘或者通过网络传输到另一远程机器,怎么办呢?------------------------------------就要把这些对象转化为字节数组,这个过程就是序列化 .
作用
序列化使得对象可以脱离程序运行而独立存在,它主要有两种用途:
- 序列化机制可以让对象地保存到磁盘上,减轻内存压力的同时,也起了持久化的作用;
比如 Web服务器中的Session对象,当有 10+万用户并发访问的,就有可能出现10万个Session对象,内存可能消化不良,于是Web容器就会把一些seesion先序列化到硬盘中,等要用了,再把保存在硬盘中的对象还原到内存中 【仅举例,实际工作中并不会这么干】
- 序列化机制让Java对象在网络中传输变得更加容易
在使用Dubbo远程调用服务框架时,需要把传输的Java对象实现Serializable接口,即让Java对象序列化,因为这样才能让对象在网络上传输。
常用API
java.io.Serializable
Serializable接口是一个标记接口,没有方法或字段。一旦实现了此接口,就标志该类的对象就是可序列化的。
public interface Serializable {
}
java.io.Externalizable
Externalizable继承了Serializable接口,还定义了两个抽象方法:writeExternal()和readExternal(),如果开发人员使用Externalizable来实现序列化和反序列化,需要重写writeExternal()和readExternal()方法
public interface Externalizable extends java.io.Serializable {
void writeExternal(ObjectOutput out) throws IOException;
void readExternal(ObjectInput in) throws IOException, ClassNotFoundException;
}
java.io.ObjectOutputStream
表示对象输出流,它的writeObject(Object obj)方法可以对指定obj对象参数进行序列化,再把得到的字节序列写到一个目标输出流中。
java.io.ObjectInputStream
表示对象输入流,它的readObject()方法,从输入流中读取到字节序列,反序列化成为一个对象,最后将其返回。
Code
实现Serializable接口
import java.io.Serializable;
/**
* @author 小工匠
* @version 1.0
* @description: TODO
* @date 2021/9/12 19:07
* @mark: show me the code , change the world
*/
public class Artisan implements Serializable {
private Integer age;
private String name;
public Integer getAge() {
return age;
}
public void setAge(Integer age) {
this.age = age;
}
public String getName() {
return name;
}
public void setName(String name) {
this.name = name;
}
}
ObjectOutputStream#writeObject 实现序列化
把Artisan对象 (必须实现Serializable 接口)设置值后,写入一个文件,即序列化
import java.io.FileOutputStream;
import java.io.IOException;
import java.io.ObjectOutputStream;
/**
* @author 小工匠
* @version 1.0
* @description: TODO
* @date 2021/9/12 19:08
* @mark: show me the code , change the world
*/
public class Test {
public static void main(String[] args) throws IOException {
ObjectOutputStream objectOutputStream = new ObjectOutputStream(new FileOutputStream("D:\\\\artisan.out"));
Artisan artisan = new Artisan();
artisan.setAge(18);
artisan.setName("artisan");
objectOutputStream.writeObject(artisan);
objectOutputStream.flush();
objectOutputStream.close();
}
}
ObjectInputStream#readObject方法实现反序列化
再把test.out文件读取出来,反序列化为Student对象
import java.io.FileInputStream;
import java.io.IOException;
import java.io.ObjectInputStream;
/**
* @author 小工匠
* @version 1.0
* @description: TODO
* @date 2021/9/12 19:09
* @mark: show me the code , change the world
*/
public class Test2 {
public static void main(String[] args) throws IOException, ClassNotFoundException {
ObjectInputStream objectInputStream = new ObjectInputStream(new FileInputStream("D:\\\\artisan.out"));
Artisan art = (Artisan) objectInputStream.readObject();
System.out.println("name="+art.getName());
}
}
序列化底层实现分析
Serializable接口,只是一个空的接口,没有方法或字段,为什么这么神奇,实现了它就可以让对象序列化了?
为了验证Serializable的作用,写个ArtisanNoSerial对象,去掉实现Serializable接口,看序列化过程怎样吧~
堆栈信息看一下
ObjectOutputStream 在序列化的时候,会判断被序列化的Object是哪一种类型,String array enum 还是 Serializable,如果都不是的话,抛出 NotSerializableException异常。所以 Serializable真的只是一个标志,一个序列化标志 。
writeObject(Object)
序列化的方法就是writeObject, debug下
writeObject直接调用的就是writeObject0()方法
public final void writeObject(Object obj) throws IOException {
......
writeObject0(obj, false);
......
}
private void writeObject0(Object obj, boolean unshared)
throws IOException
{
......
//String类型
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);
//Serializable实现序列化接口
} 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());
}
}
......
writeObject0 主要实现是对象的不同类型,调用不同的方法写入序列化数据,这里面如果对象实现了Serializable接口,就调用writeOrdinaryObject()方法~
private void writeOrdinaryObject(Object obj,
ObjectStreamClass desc,
boolean unshared)
throws IOException
{
......
//调用ObjectStreamClass的写入方法
writeClassDesc(desc, false);
// 判断是否实现了Externalizable接口
if (desc.isExternalizable() && !desc.isProxy()) {
writeExternalData((Externalizable) obj);
} else {
//写入序列化数据
writeSerialData(obj, desc);
}
.....
}
writeSerialData()实现的就是写入被序列化对象的字段数据
private void writeSerialData(Object obj, ObjectStreamClass desc)
throws IOException
{
for (int i = 0; i < slots.length; i++) {
if (slotDesc.hasWriteObjectMethod()) {
//如果被序列化的对象自定义实现了writeObject()方法,则执行这个代码块
slotDesc.invokeWriteObject(obj, this);
} else {
// 调用默认的方法写入实例数据
defaultWriteFields(obj, slotDesc);
}
}
}
defaultWriteFields()方法,获取类的基本数据类型数据,直接写入底层字节容器;获取类的obj类型数据,循环递归调用writeObject0()方法,写入数据~
private void defaultWriteFields(Object obj, ObjectStreamClass desc)
throws IOException
{
// 获取类的基本数据类型数据,保存到primVals字节数组
desc.getPrimFieldValues(obj, primVals);
//primVals的基本类型数据写到底层字节容器
bout.write(primVals, 0, primDataSize, false);
// 获取对应类的所有字段对象
ObjectStreamField[] fields = desc.getFields(false);
Object[] objVals = new Object[desc.getNumObjFields()];
int numPrimFields = fields.length - objVals.length;
// 获取类的obj类型数据,保存到objVals字节数组
desc.getObjFieldValues(obj, objVals);
//对所有Object类型的字段,循环
for (int i = 0; i < objVals.length; i++) {
......
//递归调用writeObject0()方法,写入对应的数据
writeObject0(objVals[i],
fields[numPrimFields + i].isUnshared());
......
}
}
FAQ
-
static静态变量和transient 修饰的字段是不会被序列化的
-
serialVersionUID问题
JAVA序列化的机制是通过判断类的serialVersionUID来验证版本是否一致的。在进行反序列化时,JVM会把传来的字节流中的serialVersionUID和本地相应实体类的serialVersionUID进行比较,如果相同,反序列化成功,如果不相同,就抛出InvalidClassException异常
如果确实需要修改某个类类,又想反序列化成功,怎么办呢?可以手动指定serialVersionUID的值,一般可以设置为1L或者,或者让我们的编辑器IDE生成
-
如果某个序列化类的成员变量是对象类型,则该对象类型的类必须实现序列化
-
子类实现了序列化,父类没有实现序列化,父类中的字段丢失问题
以上是关于每日一博 - Java序列化一二事儿的主要内容,如果未能解决你的问题,请参考以下文章
每日一博 - ThreadLocal VS InheritableThreadLocal VS TransmittableThreadLocal