在程序运行中,所有的遍历都是在内存中,比如,定义一个 String name = "abc",可以随时修改变量,比如把name改"de",但是一旦程序结束,变量所占用的内存就会被操作系统回收。下次重新运行程序,遍历又会变为"abc"。
定义
序列化
我们把变量从内存中变成可存储或可传输的过程叫做序列化,java序列化:把Java对象转换为字节序列的过程。
序列化之后,就可以把序列化的内容写入磁盘,或者通过网络传输到别的机器。
JSON
如果我们要在不同的编程语言之间传递对象,就必须把对象序列化为标准格式,比如XML,但更好的方法是序列化为JSON,因为JSON表示出来就是一个字符串。
反序列化
把变量内存从序列化的对象重新读到内存里称为反序列化,java反序列化将字节序列转换为Java对象的过程。
image.png序列化应用
- 对象保存在硬盘上,不但减轻了内存的压力,而且达到了持久化的目的
- 利用序列化实现远程通讯,比如dubbo框架
Java中序列化API
- java.io.ObjectOutputStream
- java.io.ObjectInputStream
- java.io.Serializable
- java.io.Externalizable
java.io.ObjectOutputStream类
对象输出流,它的writeObject(Object obj)方法可以对指定obj对象参数进行序列化,再把得到的字节序列写到一个目标输出流中。
java.io.ObjectInputStream类
表示对象输入流,
它的readObject()方法,从输入流中读取到字节序列,反序列化成为一个对象,最后将其返回。
Serializable接口
Serializable接口是一个标记接口,没有方法或字段。一旦实现了此接口,就标志该类的对象就是可序列化的。
public interface Serializable {
}
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;
}
Serializable序列化底层
例子:
@Setter
@Getter
@Slf4j
public class Test implements Serializable {
private String name;
private Integer age;
public static void main(String[] args) {
try(ObjectOutputStream objectOutputStream = new ObjectOutputStream(new FileOutputStream("D:\\test.out"));) {
Test test = new Test();
test.setName("Li");
test.setAge(27);
objectOutputStream.writeObject(test);
}catch (IOException e){
log.error("", e);
}
}
}
结果:
image.png
为了验证Serializable的作用,我们删掉Serializable关键字
@Setter
@Getter
@Slf4j
public class Test{
private String name;
private Integer age;
public static void main(String[] args) {
try(ObjectOutputStream objectOutputStream = new ObjectOutputStream(new FileOutputStream("D:\\test.out"));) {
Test test = new Test();
test.setName("Li");
test.setAge(27);
objectOutputStream.writeObject(test);
}catch (IOException e){
log.error("", e);
}
}
}
结果:
image.png
跟随输出结果我们来分析一下源码:
首先进入java.io.ObjectOutputStream#writeObject方法
public final void writeObject(Object obj) throws IOException {
//enableOverride在父类中恒为false,在子类中为true,供子类重写writeObject方法
if (enableOverride) {
writeObjectOverride(obj);
return;
}
try {
writeObject0(obj, false);
} catch (IOException ex) {
if (depth == 0) {
writeFatalException(ex);
}
throw ex;
}
}
再进入java.io.ObjectOutputStream#writeObject0
writeObject0首先是缓存替换部分,第一次进来可以直接看writeOrdinaryObject,由于Test类是实现了Serializable接口,所以会进入这个分支。
private void writeObject0(Object obj, boolean unshared)
throws IOException
{
//将输出流设置为非块模式
boolean oldMode = bout.setBlockDataMode(false);
//增加递归深度
depth++;
try {
// handle previously written and non-replaceable objects
int h;
if ((obj = subs.lookup(obj)) == null) {
writeNull();
return;
} else if (!unshared && (h = handles.lookup(obj)) != -1) {
writeHandle(h);
return;
} else if (obj instanceof Class) {
writeClass((Class) obj, unshared);//写类名
return;
} else if (obj instanceof ObjectStreamClass) {
writeClassDesc((ObjectStreamClass) obj, unshared);//写类描述
return;
}
// check for replacement object检查替代对象,要求对象重写了writeReplace方法
Object orig = obj;
Class<?> cl = obj.getClass();
ObjectStreamClass desc;
for (;;) {
// REMIND: skip this check for strings/arrays?
Class<?> repCl;
desc = ObjectStreamClass.lookup(cl, true);
if (!desc.hasWriteReplaceMethod() ||
(obj = desc.invokeWriteReplace(obj)) == null ||
(repCl = obj.getClass()) == cl)
{
break;
}
cl = repCl;
}
if (enableReplace) {
Object rep = replaceObject(obj);
if (rep != obj && rep != null) {
cl = rep.getClass();
desc = ObjectStreamClass.lookup(cl, true);
}
obj = rep;
}
// if object replaced, run through original checks a second time
if (obj != orig) {
subs.assign(orig, obj);
if (obj == null) {
writeNull();
return;
} else if (!unshared && (h = handles.lookup(obj)) != -1) {
writeHandle(h);
return;
} else if (obj instanceof Class) {
writeClass((Class) obj, unshared);
return;
} else if (obj instanceof ObjectStreamClass) {
writeClassDesc((ObjectStreamClass) obj, unshared);
return;
}
}
// remaining cases
//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) {
//由于类实现了Serialzable接口,第一次调用会先执行这个方法
writeOrdinaryObject(obj, desc, unshared);
} else {
if (extendedDebugInfo) {
throw new NotSerializableException(
cl.getName() + "\n" + debugInfoStack.toString());
} else {
throw new NotSerializableException(cl.getName());
}
}
} finally {
depth--;
bout.setBlockDataMode(oldMode);
}
}
writeOrdinaryObject这个方法首先会调用writeClassDesc(desc)写入该类的生成信息,接下来是在Externalizable和Serializable的接口出现分支,如果实现了Externalizable接口并且类描述符非动态代理,则执行writeExternalData,否则执行writeSerialData。
private void writeOrdinaryObject(Object obj,
ObjectStreamClass desc,
boolean unshared)
throws IOException
{
if (extendedDebugInfo) {
debugInfoStack.push(
(depth == 1 ? "root " : "") + "object (class \"" +
obj.getClass().getName() + "\", " + obj.toString() + ")");
}
try {
desc.checkSerialize();//是否可以序列化的检查,这个方法不允许是序列化枚举常量
bout.writeByte(TC_OBJECT);
//调用ObjectStreamClass的写入方法
writeClassDesc(desc, false);
handles.assign(unshared ? null : obj);
// 判断是否实现了Externalizable接口
if (desc.isExternalizable() && !desc.isProxy()) {
writeExternalData((Externalizable) obj);
} else {
//写入序列化数据
writeSerialData(obj, desc);
}
} finally {
if (extendedDebugInfo) {
debugInfoStack.pop();
}
}
}
接下来看writeSerialData(obj, desc)方法,首先判断是否重写了writeObject方法,如果没有,直接调用defaultWriteFields(obj, slotDesc)方法。
/**
* Writes instance data for each serializable class of given object, from
* superclass to subclass.
*/
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;
//如果被序列化的对象自定义实现了writeObject()方法,则执行这个代码块
if (slotDesc.hasWriteObjectMethod()) {
PutFieldImpl oldPut = curPut;
curPut = null;
SerialCallbackContext oldContext = curContext;
if (extendedDebugInfo) {
debugInfoStack.push(
"custom writeObject data (class \"" +
slotDesc.getName() + "\")");
}
try {
curContext = new SerialCallbackContext(obj, slotDesc);
bout.setBlockDataMode(true);
slotDesc.invokeWriteObject(obj, this);
bout.setBlockDataMode(false);
bout.writeByte(TC_ENDBLOCKDATA);
} finally {
curContext.setUsed();
curContext = oldContext;
if (extendedDebugInfo) {
debugInfoStack.pop();
}
}
curPut = oldPut;
} else {
// 调用默认的方法写入实例数据
defaultWriteFields(obj, slotDesc);
}
}
}
defaultWriteFields(obj, slotDesc)方法,这个方法会递归输出writeObject0(objVals[i],
fields[numPrimFields + i].isUnshared())
/**
* Fetches and writes values of serializable fields of given object to
* stream. The given class descriptor specifies which field values to
* write, and in which order they should be written.
*/
private void defaultWriteFields(Object obj, ObjectStreamClass desc)
throws IOException
{
Class<?> cl = desc.forClass();
if (cl != null && obj != null && !cl.isInstance(obj)) {
throw new ClassCastException();
}
//是否可以序列化检查(排除枚举的情况)
desc.checkDefaultSerialize();
int primDataSize = desc.getPrimDataSize();
if (primVals == null || primVals.length < primDataSize) {
primVals = new byte[primDataSize];
}
// 获取类的基本数据类型数据,保存到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);
for (int i = 0; i < objVals.length; i++) {
if (extendedDebugInfo) {
debugInfoStack.push(
"field (class \"" + desc.getName() + "\", name: \"" +
fields[numPrimFields + i].getName() + "\", type: \"" +
fields[numPrimFields + i].getType() + "\")");
}
try {
//递归调用writeObject0()方法,写入对应的数据
writeObject0(objVals[i],
fields[numPrimFields + i].isUnshared());
} finally {
if (extendedDebugInfo) {
debugInfoStack.pop();
}
}
}
}
序列化需注意问题点
- static静态变量和transient修饰的字段不会被序列化
- serialVersionUID生成问题
- 如果某个序列化类的成员变量是对象类型,那么该对象类型也必须序列化
- 子类实现了序列化,父类没有实现,父类中的字段会丢失
static静态变量和transient修饰的字段不会被序列化
@Setter
@Getter
@Slf4j
public class Test implements Serializable {
private static final long serialVersionUID = -2489167938558001207L;
private transient Integer age;
public static String name = "Li";
@Override
public String toString() {
return "Test{" +
"age=" + age +
","+
"name=" + name +
'}';
}
public static void main(String[] args) {
Test test = new Test();
test.setAge(27);
System.out.println(test);
try(ObjectOutputStream objectOutputStream = new ObjectOutputStream(new FileOutputStream("D:\\test.out"))) {
objectOutputStream.writeObject(test);
}catch (IOException e){
log.error("", e);
}
//修改静态变量
Test.name = "wang";
Test test2 = null;
try(ObjectInputStream objectInputStream = new ObjectInputStream(new FileInputStream("D:\\test.out"))) {
test2 = (Test) objectInputStream.readObject();
}catch (IOException |ClassNotFoundException e){
log.error("", e);
}
System.out.println(test2);
}
}
输出结果:
Test{age=27,name=Li}
Test{age=null,name=wang}
可以得出如下结论:
- static成员变量属于类级别,序列化是针对对象的,所以不能被序列化
- transient关键字,能阻止字段被序列化。Integer会被转换为null,int会被转换为0
serialVersionUID问题
序列化版本Id,每一个实现Serializable接口的类,都有一个表示序列化版本标识符的静态变量,或者默认值1L,或者等于对象的哈希码。
private static final long serialVersionUID = -2489167938558001207L;
serialVersionUID的作用
Java序列化机制是通过判断serialVersionUID来校验是否一致,在进行反序列化时,JVM会把传过来的字节流中的serialVersionUID和本地实体类的serialVersionUID进行比较,如果相同,反序列成功,不同,抛出InvalidClassException异常。
比如,我们新增加一个a属性,注释掉序列化代码,拿原先序列的来进行反序列
image.png
如果某个序列化类的成员变量是对象类型,则该对象类型的类必须实现序列化
经过测试发现,如果不实例化Test1,实际上不会报异常;只有在实例化Test1后,会出现异常。
image.png
image.png
可以看出at java.io.ObjectOutputStream.writeObject0(ObjectOutputStream.java:1184) ~[na:1.8.0_161]抛出了异常,也就是在判断对象类型时,走到了else逻辑。
image.png
子类实现了Serializable,父类没有实现Serializable接口的话,父类不会被序列化。
会导致父类属性值丢失,因为父类没有实现Serializable,父类不会被序列化。
序列化常见问题总结
- 序列化的底层是怎么实现的?
几个核心点在于,实现Serializable接口,writeObject几个核心方法,直接写入基本类型,获取obj类型数据,循环递归写入 - 序列化时,如何让某些成员不要序列化?
可以用transient关键字修饰,它可以阻止修饰的字段被序列化到文件中,在被反序列化后,transient 字段的值被设为初始值,比如int型的值会被设置为 0,对象型初始值会被设置为null。 - 在Java中,Serializable 和 Externalizable 有什么区别
Externalizable继承了Serializable,给我们提供 writeExternal() 和 readExternal() 方法, 让我们可以控制 Java的序列化机制, 不依赖于Java的默认序列化。正确实现 Externalizable 接口可以显著提高应用程序的性能。 - serialVersionUID有什么用?
JAVA序列化的机制是通过判断类的serialVersionUID来验证版本是否一致的。在进行反序列化时,JVM会把传来的字节流中的serialVersionUID和本地相应实体类的serialVersionUID进行比较,如果相同,反序列化成功,如果不相同,就抛出InvalidClassException异常。 - 是否可以自定义序列化过程, 或者是否可以覆盖 Java 中的默认序列化过程?
可以的。我们都知道,对于序列化一个对象需调用 ObjectOutputStream.writeObject(saveThisObject), 并用 ObjectInputStream.readObject() 读取对象, 但 Java 虚拟机为你提供的还有一件事, 是定义这两个方法。如果在类中定义这两种方法, 则 JVM 将调用这两种方法, 而不是应用默认序列化机制。同时,可以声明这些方法为私有方法,以避免被继承、重写或重载。 - 在Java序列化期间,哪些变量未序列化?
static静态变量和transient 修饰的字段是不会被序列化的。静态(static)成员变量是属于类级别的,而序列化是针对对象的。transient关键字修饰字段,可以阻止该字段被序列化到文件中。
网友评论