Java的序列化实现解析

= 987

本文出自:【InTheWorld的博客】java_logo

对于Java自带的对象序列化,我并没有仔细研究过它的实现机制。对于Java默认的序列化机制,它的最大优点就是简单方便。你需要做的仅仅是对需要序列化的POJO增加一句implement Serializable,最多最多再增加一行serialVersionUID。其他的事情就不用我们操心了。那么Java默认的序列化机制是如何实现的呢?答案就是——反射。

        Person person = new Person(25, "Tim");
        FileOutputStream fos = new FileOutputStream("Person.txt");
        ObjectOutputStream oos = new ObjectOutputStream(fos);
        oos.writeObject(person);

上面这段代码是典型的默认序列化使用方式。玄机就在ObjectOutputStream这个类中,它的writeObject()方法实现大致如下:

    public final void writeObject(Object obj) throws IOException {
        try {
            writeObject0(obj, false);
        } catch (IOException ex) {
            if (depth == 0) {
                writeFatalException(ex);
            }
            throw ex;
        }
    }

真正完成序列化任务的其实是writeObject0(),顺着writeObject0方法,继续看下去。这个方法其实是比较长的,writeObject0()方法使用了深度优先搜索实现了对象的序列化。比较重要的代码如下:

    private void writeObject0(Object obj, boolean unshared)
        throws IOException
    {
        boolean oldMode = bout.setBlockDataMode(false);
        depth++;
        try {
            // remaining cases
            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());
                }
            }
        } finally {
            depth--;
            bout.setBlockDataMode(oldMode);
        }
    }

writeObject0()方法调用了,会检查对象是不是实现了Serializable接口。如果实现了就会调用writeOrdinaryObject(),否者抛出异常。writeOrdinaryObject()最终会调用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();

        int primDataSize = desc.getPrimDataSize();
        if (primVals == null || primVals.length < primDataSize) {
            primVals = new byte[primDataSize];
        }
        desc.getPrimFieldValues(obj, primVals);
        bout.write(primVals, 0, primDataSize, false);

        ObjectStreamField[] fields = desc.getFields(false);
        Object[] objVals = new Object[desc.getNumObjFields()];
        int numPrimFields = fields.length - objVals.length;
        desc.getObjFieldValues(obj, objVals);
        for (int i = 0; i < objVals.length; i++) {
            try {
                writeObject0(objVals[i],
                             fields[numPrimFields + i].isUnshared());
            } finally {
        /* */
            }
        }
    }

在这个函数中,将会依次写入基本类型的字段,然后写入对象类型的字段。其中写入非基本类型字段调用了writeObject0()方法。这个序列化过程就是这样以DFS的方式递归的完成的。

使用Java默认序列化方式时,需要注意的一点是对象的静态字段和transient字段不会被序列化。这个特性在源代码中也可以看出来,这部分功能主要由ObjectStreamClass类实现。在ObjectStreamClass类中,有这样的一个静态方法getDefaultSerialFields(),它的功能就是使用反射取出一个类的各个字段。这个函数的代码如下:

    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) {
                list.add(new ObjectStreamField(clFields[i], false, true));
            }
        }
        int size = list.size();
        return (size == 0) ? NO_FIELDS :
            list.toArray(new ObjectStreamField[size]);
    }

看到mask的时候,一切都水落石出了。返回的list是排除了mask所指代的字段。而这个ObjectStreamClass是作为参数传递给defaultWriteFields()方法的,所以static和transient字段根本不会再默认方式中被序列化。这篇blog到这里就完了,我没有分析序列化后的二进制格式,暂时还没有研究的兴趣。

发表评论