0%

Serializable及其原理解析

前言

在前两天遇到了序列化对象缓慢的问题,所以对Java的序列化和反序列化进行了一点的研究,特在此写篇文章记录一下。

序列化和反序列化定义

序列化

序列化是指把一个Java对象变成二进制内存,方便网络传输、写入文件、持久化等操作,本质上就把一个Object转换成一个byte[]

反序列化

在接收到序列化后的对象,我们自然要把它还原,才好获取其内容。这就是反序列化,即把一个二进制内容(byte[])转换回Java对象。

实现

在Java中,要使一个对象具备序列化的能力,我们通常是让它实现Serializable接口。
查看源码我们可以发现,八大基本类型的装箱类型(Character、Boolean是注解implement Serializable。Byte、Short、Integer、Long、Float、Double是通过extend Number,从Number里继承的Serializable)都实现了Serializable。



而常用的HashMap、ConcurrentHashMap、Hashtable、ArrayList这些引用类型也是实现了Serializable。

那Serializable接口里到底有些什么,通过源码发现,注解很详细,但这是个空接口,里面没有定义任何方法(术语是“标记接口(Marker Interface)”)。实现标记接口仅仅是给自己贴了个标记,并没有增加任何方法。

Serializable的JavaDoc主要说了如下几件事:

1、 避免将不可信数据反序列化
2、可序列化类的所有子类都是可序列化的
3、在序列化和反序列化的过程中需要特殊处理的类必须通过实现如下方法:
private void writeObject(java.io.ObjectOutputStream out)
throws IOException
private void readObject(java.io.ObjectInputStream in)
throws IOException, ClassNotFoundException;
private void readObjectNoData()
throws ObjectStreamException;
4、尽量手动为每个标记了Serializable的类声明一个static、final、long的serialVersionUID。

其中,第一点暂时有点云里雾里,先跳过。第二点表示序列化可以被子类继承。第三点应该可以大致明白序列化的过程。
将一个Object对象转换为二进制数组,过ObjectOutputStream.writeObject()方法。其底层是writeObjectO方法,重要部分如下:

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
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());
}
}

可见,如果对象没有实现Serializable接口,则会抛出NotSerializableException异常。
像普通的Object,比如Person,则是执行writeOrdinaryObject(obj, desc, unshared)方法,该方法的javadoc说明为(Writes representation of a “ordinary” (i.e., not a String, Class, ObjectStreamClass, array, or enum constant) serializable object to the stream.”)
重要代码如下:

1
2
3
4
5
if (desc.isExternalizable() && !desc.isProxy()) {
writeExternalData((Externalizable) obj);
} else {
writeSerialData(obj, desc);
}

如果类实现了Externalizable接口,那么执行writeExternalData((Externalizable)obj )方法。
如果类实现了Serializable接口,那么执行writeSerialData(obj, desc)方法

所以我们看看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
30
31
32
33
34
35
36
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()) {
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);
}
}
}

其中,hasWriteObjectMethod()判断的应该是实现了serializable的Object是否自己重写了writeObject方法,也就是上面的第三点所提。如果没有,那么调用defaultWriteFields(obj, desc)方法。该方法内容如下:

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
30
31
32
33
34
35
36
37
38
39
40
41
42
43
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 (primDataSize > 0) {
if (primVals == null || primVals.length < primDataSize) {
primVals = new byte[primDataSize];
}
desc.getPrimFieldValues(obj, primVals);
bout.write(primVals, 0, primDataSize, false);
}

int numObjFields = desc.getNumObjFields();
if (numObjFields > 0) {
ObjectStreamField[] fields = desc.getFields(false);
Object[] objVals = new Object[numObjFields];
int numPrimFields = fields.length - objVals.length;
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(objVals[i],
fields[numPrimFields + i].isUnshared());
} finally {
if (extendedDebugInfo) {
debugInfoStack.pop();
}
}
}
}
}

参数desc是Object的Class名,系统默认写入对象的非transient部分。

回过头看看writeExternalData方法,如果一个对象实现了Externalizable接口,那么需要实现writeExternal(ObjectOutput out)readExternal(ObjectInput in)方法。而writeExternalData就是调用对象实现writeExternal(ObjectOutput out)方法。

步骤

通过这篇博客,可以知道,序列化的步骤如下:

  • 首先要创建某些OutputStream对象:OutputStream outputStream = new FileOutputStream("output.txt")
  • 将其封装到ObjectOutputStream对象内:ObjectOutputStream objectOutputStream = new ObjectOutputStream(outputStream);
  • 此后只需调用writeObject()即可完成对象的序列化,并将其发送给OutputStream:objectOutputStream.writeObject(Object);
  • 最后不要忘记关闭资源:objectOutputStream.close(), outputStream .close();

反序列化步骤如下:

  • 首先要创建某些IntputStream对象:IntputStream intputStream = new FileIntputStream("intput.txt")
  • 将其封装到ObjectIntputStream对象内:ObjectIntputStream objectIntputStream = new ObjectIntputStream(intputStream);
  • 此后只需调用readObject()即可完成对象的序列化,并将其发送给IntputStream:objectOutputStream.readObject(Object);
  • 最后不要忘记关闭资源:objectIntputStream.close(), intputStream .close();

Serializable和Externalizable

1、Serializable序列化时不会调用默认的构造器,而Externalizable序列化时会调用默认构造器

2、Serializable:一个对象想要被序列化,那么它的类就要实现 此接口,这个对象的所有属性(包括private属性、包括其引用的对象)都可以被序列化和反序列化来保存、传递。
Externalizable:他是Serializable接口的子类,有时我们不希望序列化那么多,可以使用这个接口,这个接口的writeExternal()和readExternal()方法可以指定序列化哪些属性。

-------------------本文结束 感谢阅读-------------------