java Serializbale,javaserializbale
java Serializbale,javaserializbale
JAVA 序列化
第一部分:What
Java序列化是指把Java对象保存为二进制字节码的过程,Java反序列化是指把二进制码重新转换成Java对象的过程。
那么为什么需要序列化呢?
第一种情况是:一般情况下Java对象的声明周期都比Java虚拟机的要短,实际应用中我们希望在JVM停止运行之后能够持久化指定的对象,这时候就需要把对象进行序列化之后保存。
第二种情况是:需要把Java对象通过网络进行传输的时候。因为数据只能够以二进制的形式在网络中进行传输,因此当把对象通过网络发送出去之前需要先序列化成二进制数据,在接收端读到二进制数据之后反序列化成Java对象。
第二部分:How
public class SerializableTest {
public static void main(String[] args) throws Exception {
FileOutputStream fos = new FileOutputStream("temp.out");
ObjectOutputStream oos = new ObjectOutputStream(fos);
TestObject testObject = new TestObject();
oos.writeObject(testObject);
oos.flush();
oos.close();
FileInputStream fis = new FileInputStream("temp.out");
ObjectInputStream ois = new ObjectInputStream(fis);
TestObject deTest = (TestObject) ois.readObject();
System.out.println(deTest.testValue);
System.out.println(deTest.parentValue);
System.out.println(deTest.innerObject.innerValue);
}
}
class Parent implements Serializable {
private static final long serialVersionUID = -4963266899668807475L;
public int parentValue = 100;
}
class InnerObject implements Serializable {
private static final long serialVersionUID = 5704957411985783570L;
public int innerValue = 200;
}
class TestObject extends Parent implements Serializable {
private static final long serialVersionUID = -3186721026267206914L;
public int testValue = 300;
public InnerObject innerObject = new InnerObject();
}
第三部分:Why
调用ObjectOutputStream.writeObject()和ObjectInputStream.readObject()之后究竟做了什么?temp.out文件中的二进制分别代表什么意思?
1. ObjectStreamClass类
ObjectStreamClass这个是类的序列化描述符,这个类可以描述需要被序列化的类的元数据,包括被序列化的类的名字以及序列号。可以通过lookup()方法来查找/创建在这个JVM中加载的特定的ObjectStreamClass对象。
2. 序列化:writeObject()
从源码中可以看出:
注意:
Serializbale接口是个空的接口,并没有定义任何方法,为什么需要序列化的接口只要实现Serializbale接口就能够进行序列化。
答案是:Serializable接口这是一个标识,告诉程序所有实现了”我”的对象都需要进行序列化。
总结:
3. 反序列化:readObject()
反序列化过程就是按照序列化算法来解析二进制数据。
有一个需要注意的问题就是,如果子类实现了Serializable接口,但是父类没有实现Serializable接口,这个时候进行反序列化会发生什么情况?
答:如果父类有默认构造函数的话,即使没有实现Serializable接口也不会有问题,反序列化的时候会调用默认构造函数进行初始化,否则的话反序列化的时候会抛出.InvalidClassException:异常,异常原因为no valid constructor。
第四部分:Other
1. static和transient字段不能被序列化。
序列化的时候所有的数据都是来自于ObejctStreamClass对象,在生成ObjectStreamClass的构造函数中会调用fields = getSerialFields(cl);这句代码来获取需要被序列化的字段,getSerialFields()方法实际上是调用getDefaultSerialFields()方法的,getDefaultSerialFields()实现如下:
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 |
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++) {
if ((clFields[i].getModifiers() & mask) == 0 ) {
// 如果字段既不是static也不是transient的才会被加入到需要被序列化字段列表中去
list.add( new ObjectStreamField(clFields[i], false , true ));
}
}
int size = list.size();
return (size == 0 ) ? NO_FIELDS :
list.toArray( new ObjectStreamField[size]);
}
|
从上面的代码中可以很明显的看到,在计算需要被序列化的字段的时候会把被static和transient修饰的字段给过滤掉。
在进行反序列化的时候会给默认值。
2. 如何实现自定义序列化和反序列化?
只需要被序列化的对象所属的类定义了void writeObject(ObjectOutputStream oos)和void readObject(ObjectInputStream ois)方法即可,Java序列化和反序列化的时候会调用这两个方法,那么这个功能是怎么实现的呢?
1. 在ObjectClassStream类的构造函数中有下面几行代码:
1 2 3 4 5 6 7 8 9 10 |
cons = getSerializableConstructor(cl);
writeObjectMethod = getPrivateMethod(cl, "writeObject" ,
new Class<?>[] { ObjectOutputStream. class },
Void.TYPE);
readObjectMethod = getPrivateMethod(cl, "readObject" ,
new Class<?>[] { ObjectInputStream. class },
Void.TYPE);
readObjectNoDataMethod = getPrivateMethod(
cl, "readObjectNoData" , null , Void.TYPE);
hasWriteObjectData = (writeObjectMethod != null );
|
getPrivateMethod()方法实现如下:
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 |
private static Method getPrivateMethod(Class<?> cl, String name,
Class<?>[] argTypes,
Class<?> returnType)
{
try {
Method meth = cl.getDeclaredMethod(name, argTypes);
meth.setAccessible( true );
int mods = meth.getModifiers();
return ((meth.getReturnType() == returnType) &&
((mods & Modifier.STATIC) == 0 ) &&
((mods & Modifier.PRIVATE) != 0 )) ? meth : null ;
} catch (NoSuchMethodException ex) {
return null ;
}
}
|
可以看到在ObejctStreamClass的构造函数中会查找被序列化类中有没有定义为void writeObject(ObjectOutputStream oos) 的函数,如果找到的话,则会把找到的方法赋值给writeObjectMethod这个变量,如果没有找到的话则为null。
2. 在调用writeSerialData()方法写入序列化数据的时候有
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 26 27 28 29 |
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 {
curContext.setUsed();
curContext = oldContext;
if (extendedDebugInfo) {
debugInfoStack.pop();
}
}
curPut = oldPut;
} else {
defaultWriteFields(obj, slotDesc);
}
}
}
|
首先会调用hasWriteObjectMethod()方法判断有没有自定义的writeObject(),代码如下
1 2 3 |
boolean hasWriteObjectMethod() {
return (writeObjectMethod != null );
}
|
hasWriteObjectMethod()这个方法仅仅是判断writeObjectMethod是不是等于null,而上面说了,如果用户自定义了void writeObject(ObjectOutputStream oos)这么个方法,则writeObjectMethod不为null,在if()代码块中会调用slotDesc.invokeWriteObject(obj, this);方法,该方法中会调用用户自定义的writeObject()方法。
相关文章
- 暂无相关文章
用户点评