序列化:把Java对象转化为二进制字节码的过程。
反序列化:将在序列化过程中所生成的二进制字节码转换成Java对象的过程。
为什么需要序列化呢?
主要有两个作用:
public interface Serializable {
}
Serializable 接口本身没有任何方法和属性,只是一个类可以进行序列化的标志。在对对象进行序列化时如果该类型没有实现Serializable接口就会抛出异常,实现了Serializable接口,才会对这个对象进行序列化。
使用
基本使用如下:
public class Person implements Serializable {
private String name;
private int age;
public Person(String name, int age) {
this.name = name;
this.age = age;
}
@Override
public String toString() {
return "Person{" +
"name='" + name + '\'' +
", age=" + age +
'}';
}
}
public static void main(String[] args) throws Exception {
Person person1=new Person("小明",23);
ObjectOutputStream oos=new ObjectOutputStream(new FileOutputStream("text.out"));
oos.writeObject(person1);
System.out.println(person1);//Person{name='小明', age=23}
ObjectInputStream ois=new ObjectInputStream(new FileInputStream("text.out"));
Person person2= (Person) ois.readObject();
System.out.println(person2);//Person{name='小明', age=23}
System.out.println("person1和person2是同一个对象吗?"+(person1==person2));//false
}
注意:反序列化获取的对象和原来的对象不是同一个。
借助Serializable关键字对Java对象进行序列化,反序列化非常的简单,基本的用法就不在复述,这里主要记录几个可能遗漏的知识点。
public class Person implements Serializable {
private String name;
private int age;
private Cat cat;
//...
}
//Cat.java
public class Cat{
}
如上代码,在Person类中加入Cat成员,但是Cat没有实现Serializable接口。在对Person进行序列化时会如何呢?
在对Java对象进行序列化的时候会对其每个成员进行序列化,如果这个成员是引用类型的,又没有实现Serializable接口就会抛出异常。后面源码分析的时候会讲到。
public class Person extends Base implements Serializable {
private String name;
private int age;
public Person(String name, int age) {
super(name);
this.name = name;
this.age = age;
}
}
//Base.java
public class Base {
private String desc;
//无参的构造器
public Base() {
}
public Base(String desc) {
this.desc = desc;
}
}
如果把这个无参的构造器去掉,在反序列化Person 的时候就会报错。
public class Person implements Serializable {
private String name;
private int age;
public Person(String name, int age) {
this.name = name;
this.age = age;
}
private void writeObject(ObjectOutputStream oos) throws IOException {
System.out.println("writeObject");
oos.writeUTF(name);
oos.writeInt(age);
}
private void readObject(ObjectInputStream ois) throws IOException {
System.out.println("readObject");
this.name=ois.readUTF();
this.age=ois.readInt();
}
@Override
public String toString() {
return "Person{" +
"name='" + name + '\'' +
", age=" + age +
'}';
}
}
执行结果:
writeObject
person1=Person{name='小明', age=23}
readObject
person2=Person{name='小明', age=23}
Java程序在对这个对象进行序列化的时候会通过反射尝试拿到Person 类的writeObject、readObject方法。如果用户给需要序列化的类添加了这两个方法,那么在序列化的时候就会通过反射调用它们,而不再走默认的方法。定义这两个方法可以定制一些特殊的需求,比如给某个字段加密,或者反序列化时根据条件修改某个字段等。
public class Person implements Serializable {
private String name;
private int age;
public Person(String name, int age) {
this.name = name;
this.age = age;
}
private void writeObject(ObjectOutputStream oos) throws IOException {
System.out.println("writeObject");
oos.writeUTF(name);
oos.writeInt(age);
}
private void readObject(ObjectInputStream ois) throws IOException {
System.out.println("readObject");
this.name=ois.readUTF();
this.age=ois.readInt();
System.out.println(toString());
}
private Object readResolve(){
System.out.println("readResolve");
return new Person("张三",40);
}
@Override
public String toString() {
return "Person{" +
"name='" + name + '\'' +
", age=" + age +
'}';
}
}
public static void main(String[] args) throws Exception {
Person person1=new Person("小明",23);
ObjectOutputStream oos=new ObjectOutputStream(new FileOutputStream("text.out"));
oos.writeObject(person1);
ObjectInputStream ois=new ObjectInputStream(new FileInputStream("text.out"));
Person person2= (Person) ois.readObject();
System.out.println("person2="+person2);
}
打印结果:
writeObject
readObject
Person{name='小明', age=23}
readResolve
person2=Person{name='张三', age=40}
可以看到最终返回的对象是readResolve的结果,至于readObject反序列化获取的对象就被丢弃了。这里为了方便看执行流程重写了readObject,采用默认的序列化读取结果是相同的,都会被readResolve的结果替代。
那么这个方法有什么应用场景呢?还真有,防止反序列化破坏单例。因为通过反序列化恢复的对象,是不会走构造方法的,是Java内部帮我们构建的,这时如果我们对单例对象进行序列化,然后再反序列化获取,实际上就不在是同一个对象了,这也就破坏了单例模式。此时我们就可以通过readResolve来解决这个问题。
public class DCLSingleton implements Serializable{
private static volatile DCLSingleton instance;
private DCLSingleton() {}
public static DCLSingleton getInstance() {
if(instance==null) {
synchronized (DCLSingleton.class) {
if(instance==null) {
instance=new DCLSingleton();
}
}
}
return instance;
}
//增加readResolve方法
private Object readResolve() {
return instance;
}
}
public class Person implements Serializable {
private String name;
private int age;
public Person(String name, int age) {
this.name = name;
this.age = age;
}
private Object writeReplace(){
System.out.println("writeReplace");
return new Person("小李",26);
}
@Override
public String toString() {
return "Person{" +
"name='" + name + '\'' +
", age=" + age +
'}';
}
}
Person person1=new Person("小明",23);
ObjectOutputStream oos=new ObjectOutputStream(new FileOutputStream("text.out"));
oos.writeObject(person1);
ObjectInputStream ois=new ObjectInputStream(new FileInputStream("text.out"));
Person person2= (Person) ois.readObject();
System.out.println("person2="+person2);
执行结果:
writeReplace
person2=Person{name='小李', age=26}
writeReplace会在序列化写入方法之前执行,这个方法的返回值会作为真正的即将被序列化的对象。
这里对序列化的写入进行分析,读取是类似的。
首先看看ObjectOutputStream是如何构造的。
public ObjectOutputStream(OutputStream out) throws IOException {
verifySubclass();
//bout 字节数据容器与out进行关联
bout = new BlockDataOutputStream(out);
...
//往数据容器中写入文件头信息
writeStreamHeader();
bout.setBlockDataMode(true);
...
}
writeStreamHeader方法很简单,往字节容器中写入表示序列化的Magic Number以及版本号。
protected void writeStreamHeader() throws IOException {
bout.writeShort(STREAM_MAGIC);
bout.writeShort(STREAM_VERSION);
}
//ObjectStreamConstants.java
public interface ObjectStreamConstants {
/**
* Magic number that is written to the stream header.
*/
final static short STREAM_MAGIC = (short)0xaced;
/**
* Version number that is written to the stream header.
*/
final static short STREAM_VERSION = 5;
....
}
接着,序列化的写入是从ObjectOutputStream的writeObject方法开始的,跟进看看
public final void writeObject(Object obj) throws IOException {
if (enableOverride) {//enableOverride通常是false
writeObjectOverride(obj);
return;
}
try {
//通常会执行writeObject0方法
writeObject0(obj, false);
} catch (IOException ex) {
if (depth == 0) {
writeFatalException(ex);
}
throw ex;
}
}
private void writeObject0(Object obj, boolean unshared)
throws IOException
{
// check for replacement object
Object orig = obj;
//即将被序列化的对象的Class对象
Class<?> cl = obj.getClass();
//下面创建了ObjectStreamClass对象desc,它用于描述cl这个Class对象
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 (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) {
//如果这个对象是Serializable的就会调用这个方法进行写入
writeOrdinaryObject(obj, desc, unshared);
} else {
//如果这个对象不是上面几种类型就抛出NotSerializableException异常
if (extendedDebugInfo) {
throw new NotSerializableException(
cl.getName() + "\n" + debugInfoStack.toString());
} else {
throw new NotSerializableException(cl.getName());
}
}
} finally {
depth--;
bout.setBlockDataMode(oldMode);
}
}
可以看出Serializable只是一个标记,引导方法的执行进入writeOrdinaryObject方法。
private void writeOrdinaryObject(Object obj,
ObjectStreamClass desc,
boolean unshared) throws IOException {
try {
desc.checkSerialize();
//写入标志位(0x73),表示对象写入的开始
bout.writeByte(TC_OBJECT);
//写入类的描述信息(写类元信息的过程是一个递归的过程,先写自己的,在写父类的,直至没有超类)
writeClassDesc(desc, false);
handles.assign(unshared ? null : obj);
if (desc.isExternalizable() && !desc.isProxy()) {
//如果对象实现了Externalizable接口就走writeExternalData
writeExternalData((Externalizable) obj);
} else {
//如果对象实现了Serializable接口就走writeSerialData
writeSerialData(obj, desc);
}
} finally {
if (extendedDebugInfo) {
debugInfoStack.pop();
}
}
}
Externalizable接口也是序列化接口,它继承自Serializable,并且有两个抽象方法writeExternal,readExternal。如果实现Externalizable接口来实现序列化,我们需要重写writeExternal,readExternal,自己进行序列化的写和读。通常我们会实现Serializable接口,因此会进入writeSerialData方法。
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方法,那么就会通过反射调用writeObject
if (slotDesc.hasWriteObjectMethod()) {
PutFieldImpl oldPut = curPut;
curPut = null;
SerialCallbackContext oldContext = curContext;
...
try {
curContext = new SerialCallbackContext(obj, slotDesc);
bout.setBlockDataMode(true);
slotDesc.invokeWriteObject(obj, this);
bout.setBlockDataMode(false);
bout.writeByte(TC_ENDBLOCKDATA);
}
} else {
//如果这个对象没有writeObject方法,那么就会调用默认的序列化写入方法defaultWriteFields
defaultWriteFields(obj, slotDesc);
}
}
}
这里解释了为什么在被序列化对象中添加了writeObject方法,就会走writeObject进行序列化的写入,而不是默认的方法。接下来看看defaultWriteFields方法做了什么(这部分光看代码可能比较难理解,实际上很简单,最好打断点看)。
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();
//下面几行代码是用来写入obj中的基本数据类型成员的,比如int
int primDataSize = desc.getPrimDataSize();
if (primVals == null || primVals.length < primDataSize) {
primVals = new byte[primDataSize];
}
// 获取obj中的基本数据类型的数据,并保存在primVals字节数组中
desc.getPrimFieldValues(obj, primVals);
//写入基本类型的成员
bout.write(primVals, 0, primDataSize, false);
//获取类的所有字段信息(包括基本类型和引用类型)
ObjectStreamField[] fields = desc.getFields(false);
//desc.getNumObjFields()获取类中引用类型字段的数量,然后根据这个数量窗口Object数组
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++) {
....
try {
// 对所有引用类型(Object)的字段的值递归调用writeObject0()方法进行写入
writeObject0(objVals[i],
fields[numPrimFields + i].isUnshared());
} finally {
if (extendedDebugInfo) {
debugInfoStack.pop();
}
}
}
}
这个方法主要干了两件事,1、将对象中的基本类型字段数据写入数据容器。2、遍历对象中的所用的引用类型成员,调用writeObject0进行引用类型的写入,这是一个递归的过程。
对于上面的defaultWriteFields很多字段不好用语言解释,这里给一个示例,帮助分析。对于下面的这个类:
public class Person implements Serializable {
private static final long serialVersionUID = 2L;
private String name;
private int age;
private Cat cat;
public Person(String name, int age,Cat cat) {
this.name = name;
this.age = age;
this.cat=cat;
}
}
序列化写入走到defaultWriteFields后,断点如下:
上面说了ObjectStreamClass这个类是用来描述即将被序列化的对象的Class对象的。它里面保存了很多的信息,我们修改下Person 这个类,然后查看下ObjectStreamClass的数据是什么样子的。
//Base.java
public class Base implements Serializable {
private String info;
public Base(String info) {
this.info = info;
}
}
//Person.java
public class Person extends Base {
private String name;
private int age;
private Cat cat;
//下面这几个字段不会被序列化
private transient int height;
public static String desc="人类";
private static final long serialVersionUID = 2L;
public Person(String name, int age, Cat cat, int height) {
super("info");
this.name = name;
this.age = age;
this.cat = cat;
this.height = height;
}
}
结合writeSerialData方法以及上面的截图可以知道两点:
那么问题来了?这个fields数组是如何创建的,以及如何排除static和transient修饰的字段的?
ObjectStreamClass这个类是用来描述被序列化的对象的,因此可以查看它的构建过程。
然后就是通过getDefaultSerialFields获取所有可以被序列化的字段(源码中的注释写的明明白白)。
/**
* Returns array of ObjectStreamFields corresponding to all non-static
* non-transient fields declared by given class. Each ObjectStreamField
* contains a Field object for the field it represents. If no default
* serializable fields exist, NO_FIELDS is returned.
*/
private static ObjectStreamField[] getDefaultSerialFields(Class<?> cl) {
Field[] clFields = cl.getDeclaredFields();
ArrayList<ObjectStreamField> list = new ArrayList<>();
int mask = Modifier.STATIC | Modifier.TRANSIENT;
for (int i = 0; i < clFields.length; i++) {
//如果字段既不是static也不是transient修饰的
if ((clFields[i].getModifiers() & mask) == 0) {
list.add(new ObjectStreamField(clFields[i], false, true));
}
}
int size = list.size();
return (size == 0) ? NO_FIELDS :
list.toArray(new ObjectStreamField[size]);
}
序列化源码总结:
将对象实例相关的类元描述数据写入。
递归地写入类的超类类元描述直到不再有超类。
类元描述数据写完以后,开始从最顶层的超类开始写入对象实例成员的数据(先写基本类型,在写引用类型)。
从上至下(父类到子类)递归写入实例的成员数据