21.Java序列化【草案三】

(从09年回到重庆过后,就一直在工作,时间长了惰性就慢慢起来了,公司的项目从09年忙到了现在,一直没有时间来梳理自己的东西,CSDN的Blog似乎都荒废了,不知道现在还能否坚持把Blog完成,希望有一个新的开始吧!如果读者有问题还是可直接发邮件到[email protected],我也仅仅只是想把看的、写的、学的东西总结起来,让自己有个比较完整的学习记录。本文主要针对Java的序列化相关知识,先不涉及XML序列化和Json序列化的内容,这部分内容以后再议。着色的目的是强调重点和关键的概念以及防止读者通篇阅读的视觉疲劳,也是个人的写作风格,不习惯的读者请见谅!——本来打算把全文的内容放在一篇Blog中来讲解,慢慢地就发现Java序列化的内容其实蛮多的,篇幅长了过后排版慢慢变得比较复杂,写这么多的目的就是为了把我自己理解过、分析过、验证过以及开发的时候用过的内容分享给大家,也许我不能保证在CSDN上频繁地发BLOG,但每发一次尽可能保证这些内容的营养价值。

本章目录:

1.Java中的序列化

2.序列化原理和算法——基础数据

3.深入序列化规范

4.源码分析

----ObjectStreamField

----ObjectStreamClass

----ObjectOutputStream

----ObjectInputStream

5.序列化原理和算法——面向对象


4.源码分析

  在讲解本章的内容之前,先简单回顾一下:前文讲了Java中的序列化的基本概念,并且通过输出的二进制文件内容的分析理解了Java序列化的原理和算法,同样引领读者解析了JVM的序列化规范;第二章中我们分析了ObjectStreamFieldObjectStreamClass两个类的源代码。
  其实迄今为止,所有的讲解和分析都是从一个角度在阐述——数据结构。二进制文件内容的分析实际上是让读者去理解Java序列化生成的目标数据是什么结构,而ObjectStreamFieldObjectStreamClass两个类主要用途就是用于描述对象所属类的元数据以及它所定义的成员属性的相关元数据,它们的源码中除了ObjectStreamClass中的readNonProxywriteNonProxy包含了和序列化和反序列化执行过程的内容以外,其他所有的内容都是和数据结构相关的东西。
  前边的章节我们一直讨论了下边几个问题:

  • Java的序列化是什么?
  • Java中内建序列化生成的二进制数据是什么结构?
  • 基础类型的数据在不同的情况会如何写入到二进制文件中?
  • Java的序列化规范中有些什么关键性概念?
  • Java中序列化部分的源代码定义了一些什么数据结构模型?ObjectStreamClass&ObjectStreamField
  如果读者对上边的几个问题不理解,则回顾前文的序列化讲解,从本章开始,我们将解析ObjectOutputStream以及ObjectInputStream的源代码来理解Java序列化和反序列化,通过对代码的解读彻底理解它的内部原理和算法,第4章源码分析的目标如下前文提过,这里仅做回顾
  • 详细分析Java序列化中使用的几个核心类的源代码,通过其内部执行流程换一种角度来看看Java序列化的知识;
  • 针对JVM的序列化规范中的一些疑点提供示例来加以证明,进行规范的深入理解
  • 提供Java序列化中和面向对象、最佳实践的相关内容;
  本个章节主要看看另外2个类【蓝色部分】的源代码:
  • ObjectStreamField
  • ObjectStreamClass
  • ObjectOutputStream
  • ObjectInputStream


  iii.ObjectOutputStream源码分析

  ObjectOutputStream是Java序列化机制中负责序列化的主类,在使用它的时候会用到前文提及的ObjectStreamClass以及ObjectStreamField两个核心类,接下来我们一步一步去揭开这个类的面纱:

  1)类定义:

  ObjectOutputStream的完整类定义如下:

public class ObjectOutputStream extends OutputStream implements ObjectOutput, ObjectStreamConstants

  从ObjectOutputStream的定义可以知道,它有一个父类OutputStream,实现了两个接口ObjectOutput以及ObjectStreamConstants。Java序列化中包含了很多常量值,其中前文提到的最多的是TC_*标记和SC_*标记,这些标记都是在接口ObjectStreamConstants中定义的,所以它们都属于接口常量。不仅仅是ObjectOutputStream实现了该接口,反序列化的主类ObjectInputStream同样也实现了该接口。父类OutputStream以及接口ObjectOutput的职责如下:

  • OutputStream是一个抽象类,它表示所有需要输出字节流的类的超类,输出流接受输出字节并将这些字节发送到某个接收器,所以它的子类的应用程序必须始终提供至少一种可写入输出字节的方法;
  • ObjectOutput扩展了DataOutput接口以包含对象的写入操作,DataOutput本身只包含了基础类型的输出方法,而ObjectOutput扩展了该接口,它包含了对象、数组以及String的输出方法;
  而ObjectOutputStream就是一个实现了ObjectOutput接口的OutputStream子类
  2)内部类:
  ObjectOutputStream中也定义了许多内部类,它的所有内部类的定义如下:
    private static class Caches
    public static abstract class PutField
    private class PutFieldImpl extends PutField
    private static class BlockDataOutputStream extends OutputStream implements DataOutput
    private static class HandleTable
    private static class ReplaceTable
    private static class DebugTraceInfoStack 

  Caches【缓存类】

  该类提供了序列化中的缓存机制,它只包含了两个固定的成员属性,其完整定义如下:

    private static class Caches {
        /** cache of subclass security audit results */
        static final ConcurrentMap subclassAudits =
            new ConcurrentHashMap<>();

        /** queue for WeakReferences to audited subclasses */
        static final ReferenceQueue> subclassAuditsQueue =
            new ReferenceQueue<>();
    }

  Caches类中包含了两个成员subclassAuditssubclasseAuditsQueue
  subclassAudits——该成员属性提供了一个哈希表缓存,该缓存的键类型java.io.ObjectStreamClass.WeakClassKey,注意看它的值类型是一个java.lang.Boolean类型的,从其代码注释可以知道这个哈希表缓存中保存的是所有子类的代码执行安全性检测结果;
  subclassAuditsQueue——该成员属性定义了一个“Queue队列”,它的用法和前文中ObjectStreamClass.Caches“Queue”的用法是一致的;

  PutField:

  PutField类为一个抽象类,它提供对要写入ObjectOutput持久字段的编程访问,先看看它的源代码:

    public static abstract class PutField {
        public abstract void put(String name, boolean val);
        public abstract void put(String name, byte val);
        public abstract void put(String name, char val);
        public abstract void put(String name, short val);
        public abstract void put(String name, int val);
        public abstract void put(String name, long val);
        public abstract void put(String name, float val);
        public abstract void put(String name, double val);
        public abstract void put(String name, Object val);
        @Deprecated
        public abstract void write(ObjectOutput out) throws IOException;
    }

  PutField抽象类中主要有9put方法,以及一个已经过期write方法,put方法主要将对应类型的字段值写入到持久化字段中,9个方法分别对应Java语言中的8个基础类型以及1个Object类型的字段的写入。它的参数如下:

  • name——java.lang.String
    一个类中定义的可序列化的字段名称
  • val
    需要赋值给该字段的值,它的类型不一样则调用的方法就会不同,该类中定义的put方法根据参数类型不同而实现重载
  PutFieldImpl:
  PutFieldImpl类是ObjectOutputStream类中继承PutField类的一个默认子类实现,接下来分析它的源码去理解put方法的用法,ObjectOutputStream负责的序列化是将Java对象写入到字节流,那么该默认实现就是以此为基础。
  个成员属性
        /** class descriptor describing serializable fields */
        private final ObjectStreamClass desc;
        /** primitive field values */
        private final byte[] primVals;
        /** object field values */
        private final Object[] objVals;
  • desc——java.io.ObjectStreamClass
    成员属性用于描述Java对象所属类的元数据信息
  • primVals——byte[]
    该字节数组用于保存基础数据类型
  • objVals——java.lang.Object[]
    Object类型的数组用于保存对象数据类型
  构造函数
        PutFieldImpl(ObjectStreamClass desc) {
            this.desc = desc;
            primVals = new byte[desc.getPrimDataSize()];
            objVals = new Object[desc.getNumObjFields()];
        }

  PutFieldImpl类的构造函数拥有一个参数desc,该参数的类型为java.io.ObjectStreamClassPutFieldImpl实现类会从该类的元数据中提取成员属性信息;一方面desc引用会赋值该成员属性desc,然后通过类的元数据中的字段信息初始化primVals字节数组以及objVals对象数组。区分ObjectStreamField类和PutFieldImpl类对字段元数据的描述:ObjectStreamField描述的是成员属性元数据信息【字段定义,而PutFieldImpl描述的是成员属性数据信息【字段使用

  除了已过期write方法,PutFieldImpl实现类中提供了另外两个新定义的成员函数

  writeFieldsgetFieldOffset

        void writeFields() throws IOException {
            bout.write(primVals, 0, primVals.length, false);

            ObjectStreamField[] fields = desc.getFields(false);
            int numPrimFields = fields.length - objVals.length;
            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();
                    }
                }
            }
        }

        private int getFieldOffset(String name, Class type) {
            ObjectStreamField field = desc.getField(name, type);
            if (field == null) {
                throw new IllegalArgumentException("no such field " + name +
                                                   " with type " + type);
            }
            return field.getOffset();
        }

  这里不解析write方法的用法,重点看看子类中新定义两个成员函数
  writeFields
  该方法负责将基础类型数据的值和对象类型数据的值写入字节流,看看它的实现细节:

  1. 先将被解析类中基础类型数据的数量信息写入到字节缓冲区,写入字节流的时候调用了ObjectOutputStream主类的bout成员:
                bout.write(primVals, 0, primVals.length, false);
  2. 调用desc成员属性的getFields方法获得被解析类中的所有字段元数据信息,注意getFields方法的参数为false,它表示在获取ObjectStreamField的时候,里面的每一个元素引用指向的字段元数据对象原始对象,而不是一个副本【拷贝
                ObjectStreamField[] fields = desc.getFields(false);
  3. 将字段中的数据写入到字节流中,这段代码调用了主类中的writeObject0成员函数;看看下边代码中的Debug段,它使用了主类中的成员属性extendedDebugInfo,并且结合成员属性debugInfoStack来实现启用/禁用Debug功能:
                int numPrimFields = fields.length - objVals.length;
                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();
                        }
                    }
                }
  此处留下很多疑问:主类中的bout成员属性是什么?extendedDebugInfo和debugInfoStack成员属性干什么用?成员函数writeObject0又做了些什么?这些疑问我们在下文的源代码解析中来一一解答。
  getFieldOffset
  该成员函数根据字段名称字段类型来获得该字段在所定义的类中的偏移量offset,它有2个参数:
  • name——java.lang.String
    字段名称
  • type——java.lang.Class
    字段类型
  抽象类PutField中的put方法实现【write方法过期,不提供源代码,有兴趣的读者可自行阅读】
        public void put(String name, boolean val) {
            Bits.putBoolean(primVals, getFieldOffset(name, Boolean.TYPE), val);
        }

        public void put(String name, byte val) {
            primVals[getFieldOffset(name, Byte.TYPE)] = val;
        }

        public void put(String name, char val) {
            Bits.putChar(primVals, getFieldOffset(name, Character.TYPE), val);
        }

        public void put(String name, short val) {
            Bits.putShort(primVals, getFieldOffset(name, Short.TYPE), val);
        }

        public void put(String name, int val) {
            Bits.putInt(primVals, getFieldOffset(name, Integer.TYPE), val);
        }

        public void put(String name, float val) {
            Bits.putFloat(primVals, getFieldOffset(name, Float.TYPE), val);
        }

        public void put(String name, long val) {
            Bits.putLong(primVals, getFieldOffset(name, Long.TYPE), val);
        }

        public void put(String name, double val) {
            Bits.putDouble(primVals, getFieldOffset(name, Double.TYPE), val);
        }

        public void put(String name, Object val) {
            objVals[getFieldOffset(name, Object.class)] = val;
        }
  这些put方法的默认实现中调用了getFieldOffset方法来获得字段的偏移量offset,上边所有的代码实现中总共包含了两种方式。
  第一种直接赋值【byte类型、Object类型
  看看为什么?成员属性primVals本身就是一个byte[]类型,这种类型的每一个元素都是一个byte,所以针对byte数据写入的时候,可以使用下边的代码来实现:
primVals[getFieldOffset(name, Byte.TYPE)] = val;
  而成员属性objVals本身是一个Object[]类型,这种类型的每一个元素是一个Java对象对象类型的数据和基础类型的数据在转换成字节流的方式有所不同,所以针对对象数据的写入,使用了下边的代码:
objVals[getFieldOffset(name, Object.class)] = val;
  第二种方式是调用Bits中的方法【其他类型的实现】
  类似下边这种代码:
Bits.putLong(primVals, getFieldOffset(name, Long.TYPE), val);
  这里简单提一下java.io.Bits类,该类中的方法负责将基础类型的数据转换成字节数据并且写入字节数组,或者直接从字节数组中读取字节数据还原成基础类型,该类的访问控制符为default默认域,所以只能在java.io包中使用。该类中这些方法的第二个参数都是偏移量,主要负责从字节数组中准确写入或者读取某一段字节数据。
  DebugTraceInfoStack:
  这个内部类主要负责调试在序列化过程中的对象状态信息,主要提供该开发人员Debug用。
  成员属性
        private final List stack;
  • stack——java.util.List<String>
    该成员保存了所有调试过程中的堆栈信息,使用的数据类型为一个String列表;
  因为该类中定义的方法都比较简单,所以这里直接提供它的完整代码定义,读者自行理解:
    private static class DebugTraceInfoStack {
        private final List stack;

        DebugTraceInfoStack() {
            stack = new ArrayList<>();
        }

        void clear() {
            stack.clear();
        }

        void pop() {
            stack.remove(stack.size()-1);
        }

        void push(String entry) {
            stack.add("\t- " + entry);
        }

        public String toString() {
            StringBuilder buffer = new StringBuilder();
            if (!stack.isEmpty()) {
                for(int i = stack.size(); i > 0; i-- ) {
                    buffer.append(stack.get(i-1) + ((i != 1) ? "\n" : ""));
                }
            }
            return buffer.toString();
        }
    }
  HandleTable:
  一个轻量级的哈希表,它表示一个从对象到整数类型的handle的一个映射,这个地方不知道读者是否还记得前文提到过的接口常量ObjectStreamConstants.baseWireHandle,在字节流中一个引用handle的表示方式是表示成一个int类型的整数,占用四个字节的长度,而HandleTable做的事情就是将Java对象Handle之间的关系存储起来。这个类的结构复杂,我们拆开来看。
  成员属性
        /* number of mappings in table/next available handle */
        private int size;
        /* size threshold determining when to expand hash spine */
        private int threshold;
        /* factor for computing size threshold */
        private final float loadFactor;
        /* maps hash value -> candidate handle value */
        private int[] spine;
        /* maps handle value -> next candidate handle value */
        private int[] next;
        /* maps handle value -> associated object */
        private Object[] objs;
  • size——int
    哈希表中的映射的数量
  • treshold——int
    确定何时扩大哈希表的阀值,一旦存储的数量超过了哈希表的容量则会扩充该哈希表的容量
  • loadFactor——float
    用于计算阀值的计算因子,这个因子的定义是一个常量值,使用了final修饰符;
  • spine——int[]
    当前哈希表中保存的整数类型的引用Handle值;
  • next——int[]
    下一个哈希表中保存的整数类型的引用Handle值;
  • objs——java.lang.Object[]
    和对应的引用Handle相关联的对象的
  构造函数
        HandleTable(int initialCapacity, float loadFactor) {
            this.loadFactor = loadFactor;
            spine = new int[initialCapacity];
            next = new int[initialCapacity];
            objs = new Object[initialCapacity];
            threshold = (int) (initialCapacity * loadFactor);
            clear();
        }
  该类只有一个两参构造函数
  • initialCapacity——int
    当前哈希表构造时的初始容量,如果在操作该哈希表的过程中数据容量超过了初始化的容量,则需要对哈希表进行扩容操作
  • loadFactor——float
    当前哈希表构造时计算阀值因子,该因子一旦赋值给成员属性loadFactor过后就不可更改了,针对每一个哈希表而言它计算阀值因子是一个固定值
  成员函数
        int assign(Object obj) {
            if (size >= next.length) {
                growEntries();
            }
            if (size >= threshold) {
                growSpine();
            }
            insert(obj, size);
            return size++;
        }

        int lookup(Object obj) {
            if (size == 0) {
                return -1;
            }
            int index = hash(obj) % spine.length;
            for (int i = spine[index]; i >= 0; i = next[i]) {
                if (objs[i] == obj) {
                    return i;
                }
            }
            return -1;
        }

        void clear() {
            Arrays.fill(spine, -1);
            Arrays.fill(objs, 0, size, null);
            size = 0;
        }

        int size() {
            return size;
        }

        private void insert(Object obj, int handle) {
            int index = hash(obj) % spine.length;
            objs[handle] = obj;
            next[handle] = spine[index];
            spine[index] = handle;
        }

        private void growSpine() {
            spine = new int[(spine.length << 1) + 1];
            threshold = (int) (spine.length * loadFactor);
            Arrays.fill(spine, -1);
            for (int i = 0; i < size; i++) {
                insert(objs[i], i);
            }
        }

        private void growEntries() {
            int newLength = (next.length << 1) + 1;
            int[] newNext = new int[newLength];
            System.arraycopy(next, 0, newNext, 0, size);
            next = newNext;

            Object[] newObjs = new Object[newLength];
            System.arraycopy(objs, 0, newObjs, 0, size);
            objs = newObjs;
        }

        private int hash(Object obj) {
            return System.identityHashCode(obj) & 0x7FFFFFFF;
        }
  上边的成员函数主要负责针对当前哈希表进行基本操作,看看这些方法各自的作用:
  • clear
    该方法用于将当前哈希表清空,它调用了Arraysfill方法,并且设置成员函数size0;因为该函数调用的时候,spine字节数组中的每一个元素已经有值了,所以将每一个元素的值填充为-1;而objs中的对象也有值了,所以将索引范围0到size的对象元素全部设置成null;从这一点可以知道在调用clear函数的时候清空成员属性spine、objs数组的方式使用的是“值填充”方式;
  • size
    该方法返回当前哈希表中已经存在的映射数量
  • growSpine
    该方法用于哈希表的容量扩充,主要针对objs、spine两个数组,其执行流程如下:
    a.放弃成员spine原始数组,将扩充过后的容量为“原始容量 * 2 + 1”并且初始化一个新的数组,例如原始长度为20则扩充过后为41;
    b.重新计算阀值threshold
    c.新数组中的每一个成员都初始化成-1
    d.设置objs数组中原始容量中的对象值;
  • getEntries
    该方法同样用于哈希表的容量扩充,但主要是针对objs、next两个数组,也就是针对对象数据
    a.先计算扩充过后的新容量
    b.该方法的调用过程使用了System.arraycopy方法,该方法从指定源数组中复制一个数组,复制从指定的位置开始,到目标数组的指定位置结束;
  • hash
    该方法用于返回一个固定的hash值,它的最终结果值为:System.identityHashCode( obj ) & 0x7FFFFFFF
  • insert
    该方法将根据当前Java对象和整数引用Handle来设置成员属性objs、next、spine的值;
  • assign
    该方法根据下一个Java对象和整数引用Handle来设置成员属性objs、next、spine的值,该方法调用了insert方法,如果容量不够的时候,该方法会自动扩充哈希表的容量;
  • lookup
    该方法用于查找和传入对象匹配的引用Handle,如果找不到则返回-1
  ReplaceTable:
  一个轻量级的哈希表,用于标识从一个当前对象到“替换对象”映射关系,这个类中有一个成员htabHandleTable类型,它的整体定义如下:
    private static class ReplaceTable {

        /* maps object -> index */
        private final HandleTable htab;
        /* maps index -> replacement object */
        private Object[] reps;

        /**
         * Creates new ReplaceTable with given capacity and load factor.
         */
        ReplaceTable(int initialCapacity, float loadFactor) {
            htab = new HandleTable(initialCapacity, loadFactor);
            reps = new Object[initialCapacity];
        }

        /**
         * Enters mapping from object to replacement object.
         */
        void assign(Object obj, Object rep) {
            int index = htab.assign(obj);
            while (index >= reps.length) {
                grow();
            }
            reps[index] = rep;
        }

        /**
         * Looks up and returns replacement for given object.  If no
         * replacement is found, returns the lookup object itself.
         */
        Object lookup(Object obj) {
            int index = htab.lookup(obj);
            return (index >= 0) ? reps[index] : obj;
        }

        /**
         * Resets table to its initial (empty) state.
         */
        void clear() {
            Arrays.fill(reps, 0, htab.size(), null);
            htab.clear();
        }

        /**
         * Returns the number of mappings currently in table.
         */
        int size() {
            return htab.size();
        }

        /**
         * Increases table capacity.
         */
        private void grow() {
            Object[] newReps = new Object[(reps.length << 1) + 1];
            System.arraycopy(reps, 0, newReps, 0, reps.length);
            reps = newReps;
        }
    }
  这个类中大部分方法调用的都是HandleTable的方法,它包含了许多基于哈希表映射的基本方法,就不重复解析了,请读者结合HandleTable对应的方法自行阅读理解。

  BlockDataOutputStream:
  在ObjectOutputStream类中,最重要的一个内部类要数BlockDataOutputStream类,这个类负责将缓冲区中的数据写入字节流。它可以使用两种模式写入数据到字节流:
  a.默认模式下,写入数据的时候使用和DataOutputStream相同的模式;java.io.DataOutputStream是JVM中负责将一个基础类型数据写入到输出流中的类,该类称为基础类型数据输出流,只是该类不仅仅使用于Java序列化,如果是流数据的读写,也可以使用java.io.DataOutputStreamjava.io.DataInputStream两个类,因为它们的内部结构相对简单,本文不解析这两个类的源代码。
  b.使用“Data Block”模式时,写入数据的时候会使用Data Block标记按照数据块的方式写入;
  类定义
    private static class BlockDataOutputStream extends OutputStream implements DataOutput
  这个类的定义和主类的定义有些相似,唯独不同的就是实现的接口。
  成员属性
        /** maximum data block length */
        private static final int MAX_BLOCK_SIZE = 1024;
        /** maximum data block header length */
        private static final int MAX_HEADER_SIZE = 5;
        /** (tunable) length of char buffer (for writing strings) */
        private static final int CHAR_BUF_SIZE = 256;

        /** buffer for writing general/block data */
        private final byte[] buf = new byte[MAX_BLOCK_SIZE];
        /** buffer for writing block data headers */
        private final byte[] hbuf = new byte[MAX_HEADER_SIZE];
        /** char buffer for fast string writes */
        private final char[] cbuf = new char[CHAR_BUF_SIZE];

        /** block data mode */
        private boolean blkmode = false;
        /** current offset into buf */
        private int pos = 0;

        /** underlying output stream */
        private final OutputStream out;
        /** loopback stream (for data writes that span data blocks) */
        private final DataOutputStream dout;
  该类的成员属性比较多,我们按照分类的方式来看看它的细节。
  -----------------静态常量---------------------
  BlockDataOutputStream类包含了3静态常量
  • MAX_BLOCK_SIZE——int
    输出流使用“Data Block”模式写入基础类型数据到字节流时,则该静态常量存储了每一个Data Block的边界值1024——它表示使用Data Block写入基础类型数据时每一个Data Block的最大长度不可超越这个值;
  • MAX_HEADER_SIZE——int
    当输出流使用“Data Block”模式写入基础类型数据到字节流时,则该静态常量表示每一个Data Block的头部值不可超过长度5
  • CHAR_BUF_SIZE——int
    当输出流写入字符串到字节流时,该静态常量表示一个字符缓冲区的最大长度,默认值为256
  -----------------普通常量---------------------
  • buf——byte[]
    写入常用数据Data Block数据使用的缓冲区,使用MAX_BLOCK_SIZE初始化数组长度;
  • hbuf——byte[]
    写入Data Block数据的Data Block头部值的缓冲区,使用MAX_HEADER_SIZE初始化数组长度;
  • cbuf——char[]
    写入字符串数据的缓冲区,使用CHAR_BUF_SIZE初始化数组长度;
  -----------------基础类型成员属性-------------
  • blkmode——boolean
    成员属性标识当前的写入模式是否使用了Data Block写入模式,为true则表示写入模式Data Block,反之为false默认情况并没使用Data Block模式;
  • pos——int
    该成员函数用于表示在一个写入缓冲区中的偏移量,Java序列化时每写入一个数据时需要使用偏移量在缓冲区中提取准确的二进制序列段
  -----------------对象类型成员属性-------------
  • out——java.io.OutputStream
    该成员属性表示“潜在输出流”underlying output stream,用于控制输出数据到字节流的主类;
  • dout——java.io.DataOutputStream
    成员属性表示使用Data Block模式时的主要输出流的主类loopback stream
  【*:从前文中已经见过underlying output stream的概念了,当开发员使用ObjectOutputStream序列化Java对象到字节流的时候,ObjectOutputStream在内部使用了许多额外的输出流对象,也就是说序列化Java对象到数据的过程并不只是创建了一个输出流对象,而这个时候位于ObjectOutputStream对象内部的输出流对象可以称为underlying output stream,这个输出流是开发人员无法直接通过代码引用到的对象。这里还有一个loopback stream的概念——这个流对象又表示什么呢?当系统使用Data Block方式从一个二进制序列中提取字节流段的时候,需要使用这个输出流对象,每一次写入它会根据数据的偏移量来确定写入多少个字节,而处理这种情况的输出流对象则称为loopback stream——前文已经多次提及Java中的输入输出流在读写基础类型的数据时,它读写的字节数是运行时计算的,它计算依据是需要读写的类型。举个例子:如果写入了一个boolean、long、int的三个基础类型的数据,那么输出流会执行三次,第一次使用偏移量计算来写入1个字节,然后8个字节,然后4个字节,这个时候位于内部操作这些数据的输出流可称为loopback stream,那么读取的时候如果使用的顺序为long、int、boolean则会先使用偏移量计算8个字节读取,然后4个字节,然后1个字节,这种情况不保证数据和写入时候数据对应的准确性,但也不会报错(因为读取的字节总数和写入是一致,前文有例子说明这种情况)。】
  构造函数
        BlockDataOutputStream(OutputStream out) {
            this.out = out;
            dout = new DataOutputStream(this);
        }
  该构造函数很简单,就仅仅初始化了两个对象类型的成员函数,接下来看看ObjectOutputStream中的成员函数
  -----------------Data Block模式设置-------------
  BlockDataOutputStream可以通过Data Block的方式将数据写入字节流,关于Data Block设置的成员函数如下:
        boolean setBlockDataMode(boolean mode) throws IOException {
            if (blkmode == mode) {
                return blkmode;
            }
            drain();
            blkmode = mode;
            return !blkmode;
        }
        boolean getBlockDataMode() {
            return blkmode;
        }

  上边两个成员方法用于设置Data Block模式以及获取Data Block模式,看看代码细节,关于Data Block模式的设置中,如果当前模式传入模式是相等的则不需要设置。

  -----------------DataOutput接口方法-------------

  DataOutput主要用于将基础数据换成字节数据写入,该接口的整体信息如下:


  因为BlockDataOutputStream实现了这些方法,所以这里先看看这些方法的实现细节:

  write方法

  该类中包含了三个write的基本方法:

        public void write(int b) throws IOException {
            if (pos >= MAX_BLOCK_SIZE) {
                drain();
            }
            buf[pos++] = (byte) b;
        }

        public void write(byte[] b) throws IOException {
            write(b, 0, b.length, false);
        }

        public void write(byte[] b, int off, int len) throws IOException {
            write(b, off, len, false);
        }

  上边这三个基本方法主要用于写入字节,除开这三个方法的基本写入以外,该类中还定义了另外一个write方法,该方法是write方法的重载,其定义如下:

        void write(byte[] b, int off, int len, boolean copy)
            throws IOException
        {
            if (!(copy || blkmode)) {           // write directly
                drain();
                out.write(b, off, len);
                return;
            }

            while (len > 0) {
                if (pos >= MAX_BLOCK_SIZE) {
                    drain();
                }
                if (len >= MAX_BLOCK_SIZE && !copy && pos == 0) {
                    // avoid unnecessary copy
                    writeBlockHeader(MAX_BLOCK_SIZE);
                    out.write(b, off, MAX_BLOCK_SIZE);
                    off += MAX_BLOCK_SIZE;
                    len -= MAX_BLOCK_SIZE;
                } else {
                    int wlen = Math.min(len, MAX_BLOCK_SIZE - pos);
                    System.arraycopy(b, off, buf, pos, wlen);
                    pos += wlen;
                    off += wlen;
                    len -= wlen;
                }
            }
        }

  write*方法

  ——基础类型写入

        public void writeBoolean(boolean v) throws IOException {
            if (pos >= MAX_BLOCK_SIZE) {
                drain();
            }
            Bits.putBoolean(buf, pos++, v);
        }

        public void writeByte(int v) throws IOException {
            if (pos >= MAX_BLOCK_SIZE) {
                drain();
            }
            buf[pos++] = (byte) v;
        }

        public void writeChar(int v) throws IOException {
            if (pos + 2 <= MAX_BLOCK_SIZE) {
                Bits.putChar(buf, pos, (char) v);
                pos += 2;
            } else {
                dout.writeChar(v);
            }
        }

        public void writeShort(int v) throws IOException {
            if (pos + 2 <= MAX_BLOCK_SIZE) {
                Bits.putShort(buf, pos, (short) v);
                pos += 2;
            } else {
                dout.writeShort(v);
            }
        }

        public void writeInt(int v) throws IOException {
            if (pos + 4 <= MAX_BLOCK_SIZE) {
                Bits.putInt(buf, pos, v);
                pos += 4;
            } else {
                dout.writeInt(v);
            }
        }

        public void writeFloat(float v) throws IOException {
            if (pos + 4 <= MAX_BLOCK_SIZE) {
                Bits.putFloat(buf, pos, v);
                pos += 4;
            } else {
                dout.writeFloat(v);
            }
        }

        public void writeLong(long v) throws IOException {
            if (pos + 8 <= MAX_BLOCK_SIZE) {
                Bits.putLong(buf, pos, v);
                pos += 8;
            } else {
                dout.writeLong(v);
            }
        }

        public void writeDouble(double v) throws IOException {
            if (pos + 8 <= MAX_BLOCK_SIZE) {
                Bits.putDouble(buf, pos, v);
                pos += 8;
            } else {
                dout.writeDouble(v);
            }
        }

  上边三个方法分别负责写入8基础类型的数据,其写入过程中,注意写入的判断条件:检查其写入过后的偏移量是否越过Data Block的最大值MAX_BLOCK_SIZE,如果越过了直接调用doutwrite*对应方法,未越过的情况针对不同数据类型分别写入,并且修改缓冲区中的偏移量——注:每写入一个数据其偏移量改变值会根据数据类型来,所以pos += 后边的数字为传入数据类型对应所占字节数

  ——String写入

        public void writeBytes(String s) throws IOException {
            int endoff = s.length();
            int cpos = 0;
            int csize = 0;
            for (int off = 0; off < endoff; ) {
                if (cpos >= csize) {
                    cpos = 0;
                    csize = Math.min(endoff - off, CHAR_BUF_SIZE);
                    s.getChars(off, off + csize, cbuf, 0);
                }
                if (pos >= MAX_BLOCK_SIZE) {
                    drain();
                }
                int n = Math.min(csize - cpos, MAX_BLOCK_SIZE - pos);
                int stop = pos + n;
                while (pos < stop) {
                    buf[pos++] = (byte) cbuf[cpos++];
                }
                off += n;
            }
        }

        public void writeChars(String s) throws IOException {
            int endoff = s.length();
            for (int off = 0; off < endoff; ) {
                int csize = Math.min(endoff - off, CHAR_BUF_SIZE);
                s.getChars(off, off + csize, cbuf, 0);
                writeChars(cbuf, 0, csize);
                off += csize;
            }
        }

        public void writeUTF(String s) throws IOException {
            writeUTF(s, getUTFLength(s));
        }

  String写入的成员函数主要用于写入一个String对象,在写入String的过程中,同样可以使用三种方式写入String对象到一个字节流:字节方式、字符方式、UTF字符串方式

  -----------------额外定义的批量写入-------------

  ——基础类型数组写入

  这种方式可批量写入基础类型的数据,一般其传入参数为一个数组对象

        void writeBooleans(boolean[] v, int off, int len) throws IOException {
            int endoff = off + len;
            while (off < endoff) {
                if (pos >= MAX_BLOCK_SIZE) {
                    drain();
                }
                int stop = Math.min(endoff, off + (MAX_BLOCK_SIZE - pos));
                while (off < stop) {
                    Bits.putBoolean(buf, pos++, v[off++]);
                }
            }
        }

        void writeChars(char[] v, int off, int len) throws IOException {
            int limit = MAX_BLOCK_SIZE - 2;
            int endoff = off + len;
            while (off < endoff) {
                if (pos <= limit) {
                    int avail = (MAX_BLOCK_SIZE - pos) >> 1;
                    int stop = Math.min(endoff, off + avail);
                    while (off < stop) {
                        Bits.putChar(buf, pos, v[off++]);
                        pos += 2;
                    }
                } else {
                    dout.writeChar(v[off++]);
                }
            }
        }

        void writeShorts(short[] v, int off, int len) throws IOException {
            int limit = MAX_BLOCK_SIZE - 2;
            int endoff = off + len;
            while (off < endoff) {
                if (pos <= limit) {
                    int avail = (MAX_BLOCK_SIZE - pos) >> 1;
                    int stop = Math.min(endoff, off + avail);
                    while (off < stop) {
                        Bits.putShort(buf, pos, v[off++]);
                        pos += 2;
                    }
                } else {
                    dout.writeShort(v[off++]);
                }
            }
        }

        void writeInts(int[] v, int off, int len) throws IOException {
            int limit = MAX_BLOCK_SIZE - 4;
            int endoff = off + len;
            while (off < endoff) {
                if (pos <= limit) {
                    int avail = (MAX_BLOCK_SIZE - pos) >> 2;
                    int stop = Math.min(endoff, off + avail);
                    while (off < stop) {
                        Bits.putInt(buf, pos, v[off++]);
                        pos += 4;
                    }
                } else {
                    dout.writeInt(v[off++]);
                }
            }
        }

        void writeFloats(float[] v, int off, int len) throws IOException {
            int limit = MAX_BLOCK_SIZE - 4;
            int endoff = off + len;
            while (off < endoff) {
                if (pos <= limit) {
                    int avail = (MAX_BLOCK_SIZE - pos) >> 2;
                    int chunklen = Math.min(endoff - off, avail);
                    floatsToBytes(v, off, buf, pos, chunklen);
                    off += chunklen;
                    pos += chunklen << 2;
                } else {
                    dout.writeFloat(v[off++]);
                }
            }
        }

        void writeLongs(long[] v, int off, int len) throws IOException {
            int limit = MAX_BLOCK_SIZE - 8;
            int endoff = off + len;
            while (off < endoff) {
                if (pos <= limit) {
                    int avail = (MAX_BLOCK_SIZE - pos) >> 3;
                    int stop = Math.min(endoff, off + avail);
                    while (off < stop) {
                        Bits.putLong(buf, pos, v[off++]);
                        pos += 8;
                    }
                } else {
                    dout.writeLong(v[off++]);
                }
            }
        }

        void writeDoubles(double[] v, int off, int len) throws IOException {
            int limit = MAX_BLOCK_SIZE - 8;
            int endoff = off + len;
            while (off < endoff) {
                if (pos <= limit) {
                    int avail = (MAX_BLOCK_SIZE - pos) >> 3;
                    int chunklen = Math.min(endoff - off, avail);
                    doublesToBytes(v, off, buf, pos, chunklen);
                    off += chunklen;
                    pos += chunklen << 3;
                } else {
                    dout.writeDouble(v[off++]);
                }
            }
        }

  其中批量写入的方式和基础数据写入只有一个区别,因为前面的write方法本身就支持字节数组的写入,所以批量写入的方法定义中没有writeBytes方法。

  -----------------String类型的辅助处理-------------

  这些辅助方法后边有需要再来详解,目前先提供其代码定义:

        long getUTFLength(String s) {
            int len = s.length();
            long utflen = 0;
            for (int off = 0; off < len; ) {
                int csize = Math.min(len - off, CHAR_BUF_SIZE);
                s.getChars(off, off + csize, cbuf, 0);
                for (int cpos = 0; cpos < csize; cpos++) {
                    char c = cbuf[cpos];
                    if (c >= 0x0001 && c <= 0x007F) {
                        utflen++;
                    } else if (c > 0x07FF) {
                        utflen += 3;
                    } else {
                        utflen += 2;
                    }
                }
                off += csize;
            }
            return utflen;
        }

        void writeUTF(String s, long utflen) throws IOException {
            if (utflen > 0xFFFFL) {
                throw new UTFDataFormatException();
            }
            writeShort((int) utflen);
            if (utflen == (long) s.length()) {
                writeBytes(s);
            } else {
                writeUTFBody(s);
            }
        }

        void writeLongUTF(String s) throws IOException {
            writeLongUTF(s, getUTFLength(s));
        }

        void writeLongUTF(String s, long utflen) throws IOException {
            writeLong(utflen);
            if (utflen == (long) s.length()) {
                writeBytes(s);
            } else {
                writeUTFBody(s);
            }
        }

        private void writeUTFBody(String s) throws IOException {
            int limit = MAX_BLOCK_SIZE - 3;
            int len = s.length();
            for (int off = 0; off < len; ) {
                int csize = Math.min(len - off, CHAR_BUF_SIZE);
                s.getChars(off, off + csize, cbuf, 0);
                for (int cpos = 0; cpos < csize; cpos++) {
                    char c = cbuf[cpos];
                    if (pos <= limit) {
                        if (c <= 0x007F && c != 0) {
                            buf[pos++] = (byte) c;
                        } else if (c > 0x07FF) {
                            buf[pos + 2] = (byte) (0x80 | ((c >> 0) & 0x3F));
                            buf[pos + 1] = (byte) (0x80 | ((c >> 6) & 0x3F));
                            buf[pos + 0] = (byte) (0xE0 | ((c >> 12) & 0x0F));
                            pos += 3;
                        } else {
                            buf[pos + 1] = (byte) (0x80 | ((c >> 0) & 0x3F));
                            buf[pos + 0] = (byte) (0xC0 | ((c >> 6) & 0x1F));
                            pos += 2;
                        }
                    } else {    // write one byte at a time to normalize block
                        if (c <= 0x007F && c != 0) {
                            write(c);
                        } else if (c > 0x07FF) {
                            write(0xE0 | ((c >> 12) & 0x0F));
                            write(0x80 | ((c >> 6) & 0x3F));
                            write(0x80 | ((c >> 0) & 0x3F));
                        } else {
                            write(0xC0 | ((c >> 6) & 0x1F));
                            write(0x80 | ((c >> 0) & 0x3F));
                        }
                    }
                }
                off += csize;
            }
        }

  -----------------输出流的辅助处理-------------

  实际上从上边的方法定义中可以知道,该类中的大部分方法主要是用于基础类型数据以及String的写入操作,而整个输出流中有4个辅助方法,这四个辅助方法的作用如下:

  • flush
    该方法用于将缓冲区中的数据写入到目标介质,一般的Java输出流对象中都包含了这个方法;
  • close
    该方法用于关闭当前输出流
  • drain
    该方法将当前输出流中所有缓冲的数据写入到underlying stream中,但是它不去flush“underlying stream”中的数据;
  • writeBlockHeader
    该方法用于写入Data Block头信息,针对普通Data Block以及大数据块Data Block分别写入;

        public void flush() throws IOException {
            drain();
            out.flush();
        }

        public void close() throws IOException {
            flush();
            out.close();
        }

        void drain() throws IOException {
            if (pos == 0) {
                return;
            }
            if (blkmode) {
                writeBlockHeader(pos);
            }
            out.write(buf, 0, pos);
            pos = 0;
        }

        private void writeBlockHeader(int len) throws IOException {
            if (len <= 0xFF) {
                hbuf[0] = TC_BLOCKDATA;
                hbuf[1] = (byte) len;
                out.write(hbuf, 0, 2);
            } else {
                hbuf[0] = TC_BLOCKDATALONG;
                Bits.putInt(hbuf, 1, len);
                out.write(hbuf, 0, 5);
            }
        }

  到这里所有ObjectOutputStream中的内部类就已经解析完了,接下来看看主类中的信息。

  3)成员属性

  从前文可以知道,ObjectOutputStream的内部类主要是定义了常用的序列化方法【write*、需要使用的数据结构【哈希表以及调试辅助工具,虽然前文并没有解析部分方法的细节内容,但读者可以从其代码实现中自行阅读并且加以理解,在本文后边提供示例的时候,我们会通过代码执行流程来分析序列化的基本步骤,那个时候我会尽可能把前边涉及的内容做一个整合分析。这里我们先来看看ObjectOutputStream类中的成员属性信息:

  -----------------哈希表和输出流-------------

    /** filter stream for handling block data conversion */
    private final BlockDataOutputStream bout;
    /** obj -> wire handle map */
    private final HandleTable handles;
    /** obj -> replacement obj map */
    private final ReplaceTable subs;

  • bout——java.io.ObjectOutputStream.BlockDataOutputStream
    该成员为ObjectOutputStream中的“潜在输出流”,它则以Data Block的方式写入数据到字节流;
  • handler——java.io.ObjectOutputStream.HandleTable
    该成员为一个哈希表,它表示从对象到引用的映射;
  • subs——java.io.ObjectOutputStream.RepalceTable
    该成员为一个哈希表,它表示从对象到“替换对象”的一个映射关系
  -----------------基础信息-------------

    /** stream protocol version */
    private int protocol = PROTOCOL_VERSION_2;
    /** recursion depth */
    private int depth;
    /** buffer for writing primitive field values */
    private byte[] primVals;

  • protocol——int
    当前序列化使用的字节流协议版本号
  • depth——int
    需要递归的深度,这个属性主要用于有多个父类的情况,如果出现了由上至下由下至上的递归的时候使用;
  • primVals——byte[]
    当前对象中基础类型的字段的值数据存储的字节数组;
  -----------------特殊信息-------------

    /** if true, invoke writeObjectOverride() instead of writeObject() */
    private final boolean enableOverride;
    /** if true, invoke replaceObject() */
    private boolean enableReplace;
    // values below valid only during upcalls to writeObject()/writeExternal()
    /**
     * Context during upcalls to class-defined writeObject methods; holds
     * object currently being serialized and descriptor for current class.
     * Null when not during writeObject upcall.
     */
    private SerialCallbackContext curContext;
    /** current PutField object */
    private PutFieldImpl curPut;
    /** custom storage for debug trace info */
    private final DebugTraceInfoStack debugInfoStack;
    /**
     * value of "sun.io.serialization.extendedDebugInfo" property,
     * as true or false for extended information about exception's place
     */
    private static final boolean extendedDebugInfo =
        java.security.AccessController.doPrivileged(
            new sun.security.action.GetBooleanAction(
                "sun.io.serialization.extendedDebugInfo")).booleanValue();

  • enableOverride——boolean
    如果为true,在序列化Java对象时使用writeObjectOverride方法代替writeObject方法;
  • enableReplace——boolean
    如果为true,调用replaceObject方法,否则不调用
  • curContext——java.io.SerialCallbackContext
    该成员属性为序列化的回调设置提供了上下文环境,如果Java对象重写writeObject方法,则使用该成员属性判断调用此方法的上下文环境,在JVM序列化的规范中提到过重写的部分方法必须在ObjectOutputStream内部调用,若在其他地方调用则会抛出NotActiveException异常,该成员属性则用于指向这些方法调用的上下文环境。
  • curPut——java.io.ObjectOutputStream.PutFieldImpl
    该成员属性为ObjectOutputStream内部的一个默认序列化字段时的实现实例,PutFieldImpl前文讲过,这里不重复;
  • debugInfoStack——java.io.ObjectOutputStream.DebugTraceInfoStack
    成成员属性用于辅助调试,可自定义Debug过程中堆栈信息的存储细节等;
  • extendedDebugInfo——boolean
    JVM环境中属性sun.io.serialization.extendedDebugInfo的值,该值用于启用/禁用Debug功能,这个值在定义和赋值过程调用了Java的访问控制器来检查代码的执行权限
  4)本地方法
  ObjectOutputStream类中定义了两个native本地方法,该方法用于将指定范围的float单精度浮点数以及double双精度浮点数转换成字节数据,其定义如下:
    /**
     * Converts specified span of float values into byte values.
     */
    // REMIND: remove once hotspot inlines Float.floatToIntBits
    private static native void floatsToBytes(float[] src, int srcpos,
                                             byte[] dst, int dstpos,
                                             int nfloats);
    /**
     * Converts specified span of double values into byte values.
     */
    // REMIND: remove once hotspot inlines Double.doubleToLongBits
    private static native void doublesToBytes(double[] src, int srcpos,
                                              byte[] dst, int dstpos,
                                              int ndoubles);
  前文中讲过浮点数的字节存储格式,在Java语言里,将浮点数转换成字节格式的数据调用的是这两个本地方法,它的实现部分是C语言的代码。从注释部分可以知道,它的转换也可以调用Java语言中的等价API:Float.floatToIntBitsDouble.doubleToLongBits来完成。
  5)DataOutput接口
  因为DataOutputObjectOutput接口的父接口,而ObjectOutputStream实现了ObjectOutput接口,所以先看看ObjectOutputStream中实现DataOutput接口的成员函数,因为前文已经分析过了DataOutput接口中定义的成员函数,这里仅仅该处该接口中的方法在ObjectOutputStream中的实现,读者对比下和BlockDataOutputStream中的实现看看其差异,实际上ObjectOutputStream中的实现就是调用了boutBlockDataOutputStream中的方法,这里就不重复分析了。
    public void write(int val) throws IOException {
        bout.write(val);
    }
    public void write(byte[] buf) throws IOException {
        bout.write(buf, 0, buf.length, false);
    }
    public void write(byte[] buf, int off, int len) throws IOException {
        if (buf == null) {
            throw new NullPointerException();
        }
        int endoff = off + len;
        if (off < 0 || len < 0 || endoff > buf.length || endoff < 0) {
            throw new IndexOutOfBoundsException();
        }
        bout.write(buf, off, len, false);
    }
    public void writeBoolean(boolean val) throws IOException {
        bout.writeBoolean(val);
    }
    public void writeByte(int val) throws IOException  {
        bout.writeByte(val);
    }
    public void writeShort(int val)  throws IOException {
        bout.writeShort(val);
    }
    public void writeChar(int val)  throws IOException {
        bout.writeChar(val);
    }
    public void writeInt(int val)  throws IOException {
        bout.writeInt(val);
    }
    public void writeLong(long val)  throws IOException {
        bout.writeLong(val);
    }
    public void writeFloat(float val) throws IOException {
        bout.writeFloat(val);
    }
    public void writeDouble(double val) throws IOException {
        bout.writeDouble(val);
    }
    public void writeBytes(String str) throws IOException {
        bout.writeBytes(str);
    }
    public void writeChars(String str) throws IOException {
        bout.writeChars(str);
    }
    public void writeUTF(String str) throws IOException {
        bout.writeUTF(str);
    }
  6)ObjectOutput接口
  看完了DataOutput接口,再来看看ObjectOutput接口中的方法实现,该接口的整体定义如下:

  上边的方法为ObjectOutput接口中的方法,先保留writeObject方法不讲解。因为三个write方法在DataOutput中也存在,所以该类中这三个方法如上一个章节的代码定义所示。flush方法和close方法为常用输出流方法,除了这两个方法以外还有几个辅助方法在这里一并说明:
    public void flush() throws IOException {
        bout.flush();
    }

    protected void drain() throws IOException {
        bout.drain();
    }

    public void close() throws IOException {
        flush();
        clear();
        bout.close();
    }
    public void reset() throws IOException {
        if (depth != 0) {
            throw new IOException("stream active");
        }
        bout.setBlockDataMode(false);
        bout.writeByte(TC_RESET);
        clear();
        bout.setBlockDataMode(true);
    }
  除了在BlockDataOutputStream中提到的close、flush、drain方法以外,这里多了一个reset方法。
  • flush
    该成员函数负责将缓冲区中的数据写入到字节流并且刷新缓冲区
  • close
    关闭方法,该方法会调用flush函数,并且调用clear函数【后边说明】清空两个哈希表,最后调用boutclose函数关闭underlying潜在输出流
  • drain
    该方法直接调用boutdrain方法,其作用和BlockDataOutputStream中的drain方法一样;
  • reset
    该方法的调用将会放弃所有写入到字节流中的对象的状态信息,并且将字节流重置到一个新的ObjectOutputStream对象中,当前的字节流中的偏移量也会自动重置;在写入TC_RESET标记时,会先关闭Data Block模式,等到重置信息写入过后,再将Data Block模式打开;不仅仅如此,如果depth成员属性不为0时,会抛出IOException异常信息;
  7)流协议版本
    public void useProtocolVersion(int version) throws IOException {
        if (handles.size() != 0) {
            // REMIND: implement better check for pristine stream?
            throw new IllegalStateException("stream non-empty");
        }
        switch (version) {
            case PROTOCOL_VERSION_1:
            case PROTOCOL_VERSION_2:
                protocol = version;
                break;

            default:
                throw new IllegalArgumentException(
                    "unknown version: " + version);
        }
    }
    int getProtocolVersion() {
        return protocol;
    }
  上边两个方法负责控制序列化时的流协议版本信息
  • useProtocolVersion
    设置当前对象序列化时的流协议版本信息,注意版本信息的设置过程——如果handles中有值时,会抛出IllegalStateException异常信息,也就是说如果要设置流协议版本必须保证当前的字节流中没有任何数据。在判断传入的version版本中,只支持值为PROTOCOL_VERSION_1PROTOCOL_VERSION_2两个值,若传入的version不匹配序列化流协议值,同样抛出IllegalArgumentException异常信息;
  • getProtocolVersion
    获取当前对象序列化时的流协议版本信息;
  8)putFields和writeFields
    public ObjectOutputStream.PutField putFields() throws IOException {
        if (curPut == null) {
            if (curContext == null) {
                throw new NotActiveException("not in call to writeObject");
            }
            Object curObj = curContext.getObj();
            ObjectStreamClass curDesc = curContext.getDesc();
            curPut = new PutFieldImpl(curDesc);
        }
        return curPut;
    }
    public void writeFields() throws IOException {
        if (curPut == null) {
            throw new NotActiveException("no current PutField object");
        }
        bout.setBlockDataMode(false);
        curPut.writeFields();
        bout.setBlockDataMode(true);
    }
  上述方法用于写入或者读取类定义中的字段信息。
  • putFields
    该方法调用过后会返回ObjectOutputStream.PutField对象,执行的时候它会先检查该方法的调用环境,即成员属性curContext的值,如果该值为null则表示调用此方法的上下文环境不对——putFields方法只能在ObjectOutputStream中的writeObject调用,不能在其他位置调用该方法。最后该方法从curContext获取对象类元数据信息,然后初始化curPut成员属性;
  • writeFields
    该方法在调用时必须保证curPut成员属性不为null,即上下文环境中已经存在curPut的情况下才能调用该方法,这个方法主要会调用PutFieldImpl中的writeFields方法。在调用writeFields之前会先关闭输出流的Data Block模式,调用之后再开启Data Block模式
  9)write*单元方法
  ObjectOutputStream类中有许多定义的write*前缀私有方法,用于写入不同的Java对象到字节流。前文中已经讲过了一系列的TC_*标记,而这里的“单元方法”即表示这个write方法会写入至少一个TC_*标记到目标介质【字节流中】。这里先看看每一个write方法的定义及其含义。
  writeFataException
    private void writeFatalException(IOException ex) throws IOException {
        clear();
        boolean oldMode = bout.setBlockDataMode(false);
        try {
            bout.writeByte(TC_EXCEPTION);
            writeObject0(ex, false);
            clear();
        } finally {
            bout.setBlockDataMode(oldMode);
        }
    }
  向目标介质中写入序列化过程中严重的IOException,该方法中会使用到TC_EXCEPTION标记;在写入这段信息之前会先关闭ObjectOutputStream中的“隐藏输出流【underlying stream】”Data Block模式,然后先写入TC_EXCEPTION标记,在标记之后会把异常对象ex以Object方式写入字节流【调用writeObject0方法】
  writeNull
    private void writeNull() throws IOException {
        bout.writeByte(TC_NULL);
    }
  向目标介质中写入一个空引用null,该方法中会使用TC_NULL标记,直接写入TC_NULL标记;
  writeHandle
    private void writeHandle(int handle) throws IOException {
        bout.writeByte(TC_REFERENCE);
        bout.writeInt(baseWireHandle + handle);
    }
  向目标介质中写入字节流中的对象引用Handle,该方法中会使用TC_REFERENCE标记,前文中讨论过TC_REFERENCE的用法;这个方法会先写入TC_REFERENCE标记,随后就写入对应的值——该引用的值使用baseWireHandle和传入的handle进行计算;
  writeClass
    private void writeClass(Class cl, boolean unshared) throws IOException {
        bout.writeByte(TC_CLASS);
        writeClassDesc(ObjectStreamClass.lookup(cl, true), false);
        handles.assign(unshared ? null : cl);
    }
  向目标介质中写入类实例信息,该方法中会使用TC_CLASS标记;这个方法会先写入TC_CLASS标记,然后调用writeClassDesc方法写入类元数据信息,在调用writeClassDesc的过程中,第一个参数是使用ObjectStreamClass中的lookup方法分析得到的结果;
  writeProxyDesc
    private void writeProxyDesc(ObjectStreamClass desc, boolean unshared)
        throws IOException
    {
        bout.writeByte(TC_PROXYCLASSDESC);
        handles.assign(unshared ? null : desc);

        Class cl = desc.forClass();
        Class[] ifaces = cl.getInterfaces();
        bout.writeInt(ifaces.length);
        for (int i = 0; i < ifaces.length; i++) {
            bout.writeUTF(ifaces[i].getName());
        }

        bout.setBlockDataMode(true);
        annotateProxyClass(cl);
        bout.setBlockDataMode(false);
        bout.writeByte(TC_ENDBLOCKDATA);

        writeClassDesc(desc.getSuperDesc(), false);
    }
  向目标介质中写入动态代理类的类描述信息,该方法中会使用TC_PROXYCLASSDESC标记;看看这个方法的详细过程:
  1. 先写入TC_PROXYCLASSDESC标记信息
  2. 如果使用的模式是非共享模式,则需要将desc所表示的类元数据信息插入到引用->对象的映射表中【*:插入位置是下一个位置,这里调用的是assign方法,而不是insert方法。】
  3. 再写入当前对象所属类的接口信息:先写入接口数量,其次遍历所有接口写入每一个接口名称
  4. 然后需要调用annotateProxyClass方法,在调用该方法之前开启Data Block模式,调用完成过后再关闭Data Block模式
  5. 之后再写入TC_ENDBLOCKDATA标记作为当前动态代理类的描述信息的结束
  6. 最后再调用writeClassDesc方法去写入当前对象所属类父类元数据信息,写入时不以“unshared”方式写入;
  writeNonProxyDesc
    private void writeNonProxyDesc(ObjectStreamClass desc, boolean unshared)
        throws IOException
    {
        bout.writeByte(TC_CLASSDESC);
        handles.assign(unshared ? null : desc);

        if (protocol == PROTOCOL_VERSION_1) {
            // do not invoke class descriptor write hook with old protocol
            desc.writeNonProxy(this);
        } else {
            writeClassDescriptor(desc);
        }

        Class cl = desc.forClass();
        bout.setBlockDataMode(true);
        annotateClass(cl);
        bout.setBlockDataMode(false);
        bout.writeByte(TC_ENDBLOCKDATA);

        writeClassDesc(desc.getSuperDesc(), false);
    }
  向目标介质中写入非动态代理类的类描述信息,该方法中会使用TC_CLASSDESC标记;看看这个方法的详细过程:
  1. 先写入TC_CLASSDESC标记信息;
  2. 如果使用的模式是共享模式,则将desc所表示的类元数据信息插入到引用->对象的映射表中;
  3. 然后根据使用的流协议版本调用不同的write方法:
    a.如果使用的流协议是PROTOCOL_VERSION_1,则直接调用desc成员的writeNonProxy方法,将当前引用this作为实参传入到writeNonProxy方法;
    b.如果使用的流协议不是PROTOCOL_VERSION_1,则调用当前类中的writeClassDescriptor方法;
  4. 然会需要调用annotateClass方法,在调用该方法之前开启Data Block模式,调用完成过后再关闭Data Block模式
  5. 之后再写入TC_ENDBLOCKDATA标记作为当前动态代理类的描述信息的结束;
  6. 最后调用writeClassDesc方法写入当前对象所属类的父类元数据信息,写入时同样不使用“unshared”方式写入;
  writeString
    private void writeString(String str, boolean unshared) throws IOException {
        handles.assign(unshared ? null : str);
        long utflen = bout.getUTFLength(str);
        if (utflen <= 0xFFFF) {
            bout.writeByte(TC_STRING);
            bout.writeUTF(str, utflen);
        } else {
            bout.writeByte(TC_LONGSTRING);
            bout.writeLongUTF(str, utflen);
        }
    }
  向目标介质中写入一个字符串对象信息,该方法中会使用到TC_STRINGTC_LONGSTRING标记;看看这个方法的详细过程:
  1. 写入String对象之前,系统同样会判断当前写入方式是否是“unshared”,如果不是“unshared”方式则需要在引用->对象映射中插入当前String对象;
  2. 其次调用getUTFLength函数获取String字符串的长度,获取该长度时使用UTF-8编码方式获取;
  3. 使用getUTFLength方法的返回值和0xFFFF比较:
    如果大于该值表示当前String对象是一个字符串对象——先写入TC_LONGSTRING标记,然后写入字符串的长度和内容;
    如果小于等于该值表示当前String对象就是一个普通的字符串对象——先写入TC_STRING标记,然后写入字符串的长度和内容;
  writeArray
    private void writeArray(Object array,
                            ObjectStreamClass desc,
                            boolean unshared)
        throws IOException
    {
        bout.writeByte(TC_ARRAY);
        writeClassDesc(desc, false);
        handles.assign(unshared ? null : array);

        Class ccl = desc.forClass().getComponentType();
        if (ccl.isPrimitive()) {
            if (ccl == Integer.TYPE) {
                int[] ia = (int[]) array;
                bout.writeInt(ia.length);
                bout.writeInts(ia, 0, ia.length);
            } else if (ccl == Byte.TYPE) {
                byte[] ba = (byte[]) array;
                bout.writeInt(ba.length);
                bout.write(ba, 0, ba.length, true);
            } else if (ccl == Long.TYPE) {
                long[] ja = (long[]) array;
                bout.writeInt(ja.length);
                bout.writeLongs(ja, 0, ja.length);
            } else if (ccl == Float.TYPE) {
                float[] fa = (float[]) array;
                bout.writeInt(fa.length);
                bout.writeFloats(fa, 0, fa.length);
            } else if (ccl == Double.TYPE) {
                double[] da = (double[]) array;
                bout.writeInt(da.length);
                bout.writeDoubles(da, 0, da.length);
            } else if (ccl == Short.TYPE) {
                short[] sa = (short[]) array;
                bout.writeInt(sa.length);
                bout.writeShorts(sa, 0, sa.length);
            } else if (ccl == Character.TYPE) {
                char[] ca = (char[]) array;
                bout.writeInt(ca.length);
                bout.writeChars(ca, 0, ca.length);
            } else if (ccl == Boolean.TYPE) {
                boolean[] za = (boolean[]) array;
                bout.writeInt(za.length);
                bout.writeBooleans(za, 0, za.length);
            } else {
                throw new InternalError();
            }
        } else {
            Object[] objs = (Object[]) array;
            int len = objs.length;
            bout.writeInt(len);
            if (extendedDebugInfo) {
                debugInfoStack.push(
                    "array (class \"" + array.getClass().getName() +
                    "\", size: " + len  + ")");
            }
            try {
                for (int i = 0; i < len; i++) {
                    if (extendedDebugInfo) {
                        debugInfoStack.push(
                            "element of array (index: " + i + ")");
                    }
                    try {
                        writeObject0(objs[i], false);
                    } finally {
                        if (extendedDebugInfo) {
                            debugInfoStack.pop();
                        }
                    }
                }
            } finally {
                if (extendedDebugInfo) {
                    debugInfoStack.pop();
                }
            }
        }
    }
  向目标介质中写入一个数组array对象信息,该方法中会使用到TC_ARRAY标记;看看这个方法的详细过程:
  1. 先写入TC_ARRAY标记信息
  2. 然后写入这个数组的类描述信息,写入方式使用“unshared”方式;
  3. 如果使用的模式是共享模式,则将desc所表示的类元数据信息插入到引用->对象的映射表中;
  4. 随后获取当前数组中元素的类型:
    a.如果元素是基础类型——先写入该数组的长度,其次写入这些数组中所有元素的值;
    b.如果元素是对象类型——先写入该数组的长度,其次调用writeObject0方法写入这些数组中每一个对象元素;
  writeEnum
    private void writeEnum(Enum en,
                           ObjectStreamClass desc,
                           boolean unshared)
        throws IOException
    {
        bout.writeByte(TC_ENUM);
        ObjectStreamClass sdesc = desc.getSuperDesc();
        writeClassDesc((sdesc.forClass() == Enum.class) ? desc : sdesc, false);
        handles.assign(unshared ? null : en);
        writeString(en.name(), false);
    }
  向目标介质中写入一个Enum枚举常量信息,该方法中会使用到TC_ENUM标记;看看这个方法的详细过程:
  1. 先写入TC_ENUM标记信息
  2. 然后获取当前类的父类元数据信息判断是否枚举类信息,如果是枚举类则写入枚举类信息,否则写入当前类的父类信息——从JDK 1.5引入了枚举类型过后,这些枚举信息都继承于Enum.class,所以判断一个类是否枚举类使用这种方式判断;
  3. 如果使用的模式是共享模式,则将desc所表示的类元数据信息插入到引用->对象的映射表中;
  4. 最后将调用枚举类型中的name()方法,将枚举类型的字符串字面量以String方法写入字节流;
  writeOrginaryObject
    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);
            writeClassDesc(desc, false);
            handles.assign(unshared ? null : obj);
            if (desc.isExternalizable() && !desc.isProxy()) {
                writeExternalData((Externalizable) obj);
            } else {
                writeSerialData(obj, desc);
            }
        } finally {
            if (extendedDebugInfo) {
                debugInfoStack.pop();
            }
        }
    }
  向目标介质中写入一个Java对象的信息,该方法中会使用到TC_OBJECT标记;看看这个方法的详细过程:
  1. 在写入Java对象信息之前,需要先调用ObjectStreamClass检查当前对象是否是一个可序列化对象;
  2. 其次写入TC_OBJECT标记;
  3. 随后调用writeClassDesc方法写入当前对象所属类的类描述信息
  4. 如果使用的模式是共享模式,则将desc所表示的类元数据信息插入到引用->对象的映射表中;
  5. 随后判断当前Java对象的序列化语义
    a.如果当前对象不是一个动态代理类并且是实现了外部化的,则调用writeExternalData方法写入对象信息;
    b.如果当前对象是一个实现了Serializable接口的,则调用writeSerialData方法写入对象信息;
  10)其他write*单元方法
  除了上边提及到的write*单元方法以外,该类中还包含了其他几个比较特殊的write单元方法,在特殊情况下会被调用。
  writeExternalData
    private void writeExternalData(Externalizable obj) throws IOException {
        PutFieldImpl oldPut = curPut;
        curPut = null;

        if (extendedDebugInfo) {
            debugInfoStack.push("writeExternal data");
        }
        SerialCallbackContext oldContext = curContext;
        try {
            curContext = null;
            if (protocol == PROTOCOL_VERSION_1) {
                obj.writeExternal(this);
            } else {
                bout.setBlockDataMode(true);
                obj.writeExternal(this);
                bout.setBlockDataMode(false);
                bout.writeByte(TC_ENDBLOCKDATA);
            }
        } finally {
            curContext = oldContext;
            if (extendedDebugInfo) {
                debugInfoStack.pop();
            }
        }

        curPut = oldPut;
    }
  该方法在序列化的Java对象定义了writeExternal()的时候调用,也就是说只有当一个Java对象实现了外部化过后才调用该方法;看看这个方法的详细过程:
  1. 该方法会判断目前使用的字节流协议
    a.如果使用的是PROTOCOL_VERSION_1协议,则直接调用可序列化对象中的writeExternal方法;
    b.如果不是使用的PROTOCOL_VERSION_1协议,则先开启Data Block模式,再调用writeExternal方法,调用过后关闭Data Block模式,并且在最后追加TC_ENDBLOCKDATA标记;
  2. 注意这个方法有一个切换上下文环境的过程,定义了一个新的SerialCallbackContext类型的引用oldContext用于保存执行之前的环境,在finally块中将当前环境复原——也就是说在调用writeExternal方法时,curContext的值为null
  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;
            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);
            }
        }
    }
  该方法主要负责写入Java对象的数据信息,比如字段值和相关引用等,写入的时候会从顶级父类从上至下递归执行;看看这个方法的详细过程:
  1. 在序列化当前对象之前,先从类描述信息中获取ClassDataSlot信息;
  2. 判断可序列化对象是否重写了writeObject方法:
    a.如果没有重写该方法,则调用defaultWriteFields方法写入当前对象中的所有字段信息
    b.如果重写了该方法,则先开启Data Block模式,再调用writeObject方法,调用过后关闭Data Block模式,并且在最后追加TC_ENDBLOCKDATA标记;
  3. 注该方法执行过程同样有一个切换上下文环境的过程,读者自诩阅读和curContext相关的代码行就可以理解了;
  11)两个default
  ObjectOutputStream中定义了两个default默认方法,用于默认调用。
  defaultWriteObject
    public void defaultWriteObject() throws IOException {
        if ( curContext == null ) {
            throw new NotActiveException("not in call to writeObject");
        }
        Object curObj = curContext.getObj();
        ObjectStreamClass curDesc = curContext.getDesc();
        bout.setBlockDataMode(false);
        defaultWriteFields(curObj, curDesc);
        bout.setBlockDataMode(true);
    }
  该方法负责将static以及transient字段写入到字节流;看看这个方法的详细过程:
  1. 这个方法只能在writeObject方法内调用,如果检查curContext环境发现当前方法的调用不是在writeObject方法中,则抛出NotActiveException异常信息;
  2. 从上下文环境中获取对象和类描述信息
  3. 最后关闭Data Block模式,调用defaultWriteFields方法将字段信息写入到字节流,调用之后再开启Data Block模式
  defaultWriteFields
    private void defaultWriteFields(Object obj, ObjectStreamClass desc)
        throws IOException
    {
        // REMIND: perform conservative isInstance check here?
        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++) {
            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();
                }
            }
        }
    }
  该方法负责读取Java对象中的字段数据,并且将字段数据写入到字节流;看看这个方法的详细过程:
  1. 该方法会先检查可序列化的语义;
  2. 然后获取该对象中所有基础类型字段的,获得过后写入这些基础类型数据到字节流;
  3. 完成基础类型的字段值写入过程,再调用writeObject0方法写入对象类型的字段的值;
  12)write*控制方法
  除了上边提到的defaultwrite*单元方法,该类中还包含了许多write*控制方法,这些write*控制方法主要用于根据不同的情况决定调用上边的哪一个write*单元方法,它除了能够控制流程以外,一般没有实质性实现代码
  writeClassDescriptor
    protected void writeClassDescriptor(ObjectStreamClass desc)
        throws IOException
    {
        desc.writeNonProxy(this);
    }
  该方法可让子类重写,它只是调用了writeNonProxy方法,父类的默认实现传入this参数给这个方法;
  writeTypeString
    void writeTypeString(String str) throws IOException {
        int handle;
        if (str == null) {
            writeNull();
        } else if ((handle = handles.lookup(str)) != -1) {
            writeHandle(handle);
        } else {
            writeString(str, false);
        }
    }
  该方法主要用于判断一个String类型的对象以什么方式写入
  • 如果传入的String引用是一个null引用,则调用writeNull方法;
  • 如果能够在字符串常量池中找到传入的String对象,则调用writeHandle方法;
  • 上边两个条件都不满足时,直接调用writeString使用“unshared”方式写入传入的String对象
  writeClassDesc
    private void writeClassDesc(ObjectStreamClass desc, boolean unshared)
        throws IOException
    {
        int handle;
        if (desc == null) {
            writeNull();
        } else if (!unshared && (handle = handles.lookup(desc)) != -1) {
            writeHandle(handle);
        } else if (desc.isProxy()) {
            writeProxyDesc(desc, unshared);
        } else {
            writeNonProxyDesc(desc, unshared);
        }
    }
  该方法主要用于判断当前的类描述符使用什么方式写入
  • 如果传入的类描述信息是一个null引用,则调用writeNull方法;
  • 如果使用了“unshared”方式,并且可以在对象池中找到传入的对象信息,则调用writeHandle
  • 如果传入的类是一个动态代理类,则调用writeProxyDesc方法;
  • 以上条件都不满足时,则调用writeNonProxyDesc方法;
  13)核心write方法
  writeObject
    public final void writeObject(Object obj) throws IOException {
        if (enableOverride) {
            writeObjectOverride(obj);
            return;
        }
        try {
            writeObject0(obj, false);
        } catch (IOException ex) {
            if (depth == 0) {
                writeFatalException(ex);
            }
            throw ex;
        }
    }
  该方法为外部调用的核心API,它会调用另外两个内部方法writeObjectOverridewriteObject0
  writeObjectOverride
    protected void writeObjectOverride(Object obj) throws IOException {
    }
  该方法的默认实现为空实现,这个方法存在的目的主要是用于子类重写
  writeObject0
    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
            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
            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);
        }
    }
  这个方法在ObjectOutputStream中才是writeObject方法的核心方法,主要用于写入对象信息。它的详细过程如下:
  1. 关闭输出流的Data Block模式,并且将原始模式赋值给变量oldMode
  2. 如果当前传入对象在“替换哈希表【ReplaceTable】”中无法找到,则调用writeNull方法并且返回;
    如果当前写入方式是“unshared”方式,并且可以在“引用哈希表【HandleTable】”中找到该引用,则调用writeHandle方法并且返回;
    如果当前传入对象是特殊类型Class类型,则调用writeClass方法并且返回;
    如果当前传入对象是特殊类型ObjectStreamClass,则调用writeClassDesc方法并且返回;
  3. 以上条件都不满足【传入对象不是ObjectStreamClass和Class类型,而且使用了“unshared”方式,“替换哈希表”中没有该记录】,需要检查替换对象,在检查替换对象时需要跳过string/arrays类型,通过循环方式查找“替换对象”
    【*:到这里可以理解Java序列化中的替换对象是什么概念了——前文JVM的序列化规范中提到过“替换”对象的概念,实际上替换对象就是在可序列化方法中重写了writeReplace方法的Java对象。】
  4. 通过检查成员属性enableReplace的值判断当前对象是否启用了“替换”功能Replace功能】
  5. 对象替换过后,则需要对原始对象进行二次检查,先将替换对象插入到“替换哈希表”中,然后执行和第二步一模一样的检查来检查原始对象;
  6. 以上执行都完成过后,处理剩余对象类型:
    如果传入对象为String类型,调用writeString方法将数据写入字节流;
    如果传入对象为Array类型,调用writeArray方法将数据写入字节流;
    如果传入对象为Enum类型,调用writeEnum方法将数据写入字节流;
    如果传入对象实现了Serializable接口,调用writeOrdinaryObject方法将数据写入字节流;
    以上条件都不满足时则抛出NotSerializableException异常信息;
  7. 执行完上述代码过后,将输出流的Data Block模式还原;
  *:这里的代码执行过程可参考JVM序列化规范中的writeObject方法的概念描述,前一章中描述了writeObject的详细概念流程,对比其概念流程和上边writeObject0的代码流程请读者彻底理解ObjectOutputStream中的writeObject方法究竟做了些什么。
  writeStreamHeader
    protected void writeStreamHeader() throws IOException {
        bout.writeShort(STREAM_MAGIC);
        bout.writeShort(STREAM_VERSION);
    }
  该方法负责写入魔数序列化版本信息;
  writeUnshared
    public void writeUnshared(Object obj) throws IOException {
        try {
            writeObject0(obj, true);
        } catch (IOException ex) {
            if (depth == 0) {
                writeFatalException(ex);
            }
            throw ex;
        }
    }
  以“unshared”的方式写入Java对象到字节流,直接调用writeObject0方法将对象数据写入字节流;
  14)子类实现方法
  annotateClass
    protected void annotateClass(Class cl) throws IOException {
    }
  annotateProxyClass
    protected void annotateProxyClass(Class cl) throws IOException {
    }
  replaceObject
    protected Object replaceObject(Object obj) throws IOException {
        return obj;
    }
  上述三个方法annotateClass、annotateProxyClass以及replaceObject三个方法主要是提供给子类实现,这里看看这三个方法的用途信息:
  • annotateClass
    子类可以实现该方法,这样将允许类中的数据存储在字节流中,默认情况下这个方法什么也不做,和该方法对应的ObjectInputStream中的方法为resolveClass。这个方法对于字节流中的类只会调用唯一的一次类名以及类签名信息都会写入到字节流中,这个方法会使用ObjectOutputStream类自由写入任何格式的类信息,例如默认的字节文件。不仅仅如此,ObjectInputStream类中的resolveClass方法会读取被annotateClass方法写入的任何基础数据对象数据
  • annotateProxyClass
    子类可以实现该方法用于定制字节流中用于描述动态代理类的数据信息;这个方法针对每一个动态代理方法只会调用唯一的一次,它的默认实现却什么也不做;
  • replaceObject
    前文多次提到“替换对象”——“替换对象”实际上就是重写了该方法的对象。这个方法允许ObjectOutputStream可信任子类在序列化过程中替换另外一个对象,默认情况下“替换对象”功能是未开启的,一旦调用了enableReplaceObject方法后就启用了序列化中的“替换对象”功能。enableReplaceObject方法会检查字节流中的替换请求是否可信任的,序列化字节流中第一个写入的可匹配对象将会传给replaceObject方法,接下来写入字节流的对象的引用也会被原始对象调用replaceObject方法替换掉。必须要确保的是对象的私有状态不会暴露在外,只有可信任的字节流可调用replaceObject方法;
    a.ObjectOutputStreamwriteObject方法会使用一个Object【实现了Serializable接口作为参数,它允许可序列化对象被可序列化对象替换;
    b.当一个子类是一个替换对象,它必须保证序列化替换中的必须替换操作或者这些可替换对象的每一个字段和引用指向的原始对象是兼容的。如果对象类型并不是字段的子类或者数组元素子类,则中断序列化过程抛出异常,这种情况下不会保存该对象;
    c.这个方法仅仅在第一次遇到同类型对象时调用,所有之后的引用将重定向到新的对象中,这个方法将在执行过后返回原始对象或者被替换的新对象;
    d.null可以作为一个对象被替换,但是它有可能引起NullReferenceException异常;
  enableReplaceObject
    protected boolean enableReplaceObject(boolean enable)
        throws SecurityException
    {
        if (enable == enableReplace) {
            return enable;
        }
        if (enable) {
            SecurityManager sm = System.getSecurityManager();
            if (sm != null) {
                sm.checkPermission(SUBSTITUTION_PERMISSION);
            }
        }
        enableReplace = enable;
        return !enableReplace;
    }
  调用这个方法会启用序列化中的“替换”功能
  • 如果该功能已经启用,并且传入参数为true,则什么都不做直接返回;
  • 如果想要启用“替换”功能,则需要调用Java安全管理器,并且检查权限SUBSTITUTION_PERMISSION
  15)构造方法
  该类的构造方法如下:
    public ObjectOutputStream(OutputStream out) throws IOException {
        verifySubclass();
        bout = new BlockDataOutputStream(out);
        handles = new HandleTable(10, (float) 3.00);
        subs = new ReplaceTable(10, (float) 3.00);
        enableOverride = false;
        writeStreamHeader();
        bout.setBlockDataMode(true);
        if (extendedDebugInfo) {
            debugInfoStack = new DebugTraceInfoStack();
        } else {
            debugInfoStack = null;
        }
    }
    protected ObjectOutputStream() throws IOException, SecurityException {
        SecurityManager sm = System.getSecurityManager();
        if (sm != null) {
            sm.checkPermission(SUBCLASS_IMPLEMENTATION_PERMISSION);
        }
        bout = null;
        handles = null;
        subs = null;
        enableOverride = true;
        debugInfoStack = null;
    }
  ObjectOutputStream类的构造方法有两个,一个public单参数构造函数,一个protected无参构造函数,看看两个构造函数的详细流程:
  protected构造函数:
  1. 调用安全管理器SecurityManager,并且使用安全管理器检查权限SUBCLASS_IMPLEMENTATION_PERMISSION
  2. 设置成员属性的默认值:
    bout——nullhandles——nullsubs——nullenableOverride——truedebugInfoStack——null
  public构造函数:
  1. 调用verifySubclass方法验证子类信息;
  2. 初始化bout成员属性,实例化一个BlockDataOutputStream
  3. 初始化handlessubs,创建两个对应的实例;
  4. enableOverride成员属性的值设置成false
  5. 调用writeStreamHeader方法写入魔数和序列化版本信息;
  6. 开启Data Block模式写入信息;
  7. 如果启用了调试模式,则需要实例化debugInfoStack
  16)其他方法
  verifySubclass
    private void verifySubclass() {
        Class cl = getClass();
        if (cl == ObjectOutputStream.class) {
            return;
        }
        SecurityManager sm = System.getSecurityManager();
        if (sm == null) {
            return;
        }
        processQueue(Caches.subclassAuditsQueue, Caches.subclassAudits);
        WeakClassKey key = new WeakClassKey(cl, Caches.subclassAuditsQueue);
        Boolean result = Caches.subclassAudits.get(key);
        if (result == null) {
            result = Boolean.valueOf(auditSubclass(cl));
            Caches.subclassAudits.putIfAbsent(key, result);
        }
        if (result.booleanValue()) {
            return;
        }
        sm.checkPermission(SUBCLASS_IMPLEMENTATION_PERMISSION);
    }
  该方法主要用于处理缓存信息,在调用过程会使用Java安全管理器检查代码执行权限;
  auditSubclass
    private static boolean auditSubclass(final Class subcl) {
        Boolean result = AccessController.doPrivileged(
            new PrivilegedAction() {
                public Boolean run() {
                    for (Class cl = subcl;
                         cl != ObjectOutputStream.class;
                         cl = cl.getSuperclass())
                    {
                        try {
                            cl.getDeclaredMethod(
                                "writeUnshared", new Class[] { Object.class });
                            return Boolean.FALSE;
                        } catch (NoSuchMethodException ex) {
                        }
                        try {
                            cl.getDeclaredMethod("putFields", (Class[]) null);
                            return Boolean.FALSE;
                        } catch (NoSuchMethodException ex) {
                        }
                    }
                    return Boolean.TRUE;
                }
            }
        );
        return result.booleanValue();
    }
  该方法主要用于编辑缓存信息,同样在执行过程会调用Java中的安全管理器检查代码执行权限;
  clear
    private void clear() {
        subs.clear();
        handles.clear();
    }
  清空subs替换哈希表handles引用哈希表

  iv.ObjectInputStream源码分析
  上文解析了ObjectOutputStream类的代码,对于ObjectInputStream的解析本文不会ObjectOutputStream类中的方法这样详细解析,因为ObjectInputStream类里面的大部分方法以及接口刚好是和ObjectOutputStream类中的方法对应,本文只提供一些比较特殊的代码解析,至于ObjectInputStream类的其他源代码希望读者自行阅读并理解。下一个章节会提供Java序列化的应用例子,到时候再来详细看看ObjectOutputStreamObjectInputStream两个类的使用;
  1)类定义:
  ObjectInputStream的类定义如下:
public class ObjectInputStream extends InputStream implements ObjectInput, ObjectStreamConstants
  该类的定义和ObjectOutputStream的定义大同小异,这里不重复ObjectStreamConstants接口了,看看InputStream类和ObjectInput接口的职责:
  • InputStream是一个抽象类,它表示字节输入流的所有类的超类,需要定义InputStream子类的应用程序必须总是提供返回下一个输入字节的方法;
  • ObjectInput接口扩展DataInput接口包含对象的操作,DataInput接口包括了基本类型的输入方法,ObjectInput扩展了该接口,以包含对象、数组和String的输入方法;
  ObjectInputStream就是一个实现了ObjectInput接口的InputStream的子类;
  2)内部类区别
  ObjectInputStream类中的内部类定义如下:
    private static class Caches
    public static abstract class GetField
    private class GetFieldImpl extends GetField
    private static class ValidationList
    private static class PeekInputStream extends InputStream
    private class BlockDataInputStream extends InputStream implements DataInput
    private static class HandleTable
  Caches
  该内部类的定义和ObjectOutputStream类中的缓存类定义是一致的,它拥有两个成员subclassAuditssubclassAuditsQueue,其类型这里就不讲解了;
  GetField/GetFieldImpl
  该内部类和PutField/PutFieldImpl配对使用,主要用于处理对象中字段信息读取操作:
  BlockDataInputStream
  这个类和BlockDataOutputStream类对应,主要用于读取基础类型的数据信息,它属于ObjectInputStream中的“隐藏输入流【underlying stream】”
  成员属性
        /** end offset of valid data in buf, or -1 if no more block data */
        private int end = -1;
        /** number of bytes in current block yet to be read from stream */
        private int unread = 0;
  BlockDataInputStream中多了两个偏移量,除了基本偏移量以外还包含另外两个成员属性偏移量:
  • end
    表示读取某一段Data Block时该数据块结束偏移量,如果该数据块中没有合法数据,则返回-1
  • unread
    用来统计当前Data Block未读取的字节的数量
  成员函数
  currentBlockRemaining
        int currentBlockRemaining() {
            if (blkmode) {
                return (end >= 0) ? (end - pos) + unread : 0;
            } else {
                throw new IllegalStateException();
            }
        }
  因为是读取,该方法主要用于计算在Data Block模式下一个数据块中还未使用的字节的数量,如果当前模式不是Data Block模式,则抛出IllegalStateException异常信息;
  skip*方法
void skipBlockData() throws IOException {
    if (!blkmode) {
        throw new IllegalStateException("not in block data mode");
    }
    while (end >= 0) {
        refill();
    }
}
public long skip(long len) throws IOException {
    long remain = len;
    while (remain > 0) {
        if (blkmode) {
            if (pos == end) {
                refill();
            }
            if (end < 0) {
                break;
            }
            int nread = (int) Math.min(remain, end - pos);
            remain -= nread;
            pos += nread;
        } else {
            int nread = (int) Math.min(remain, MAX_BLOCK_SIZE);
            if ((nread = in.read(buf, 0, nread)) < 0) {
                break;
            }
            remain -= nread;
        }
    }
    return len - remain;
}
  因为是输入流,所以在读取信息过程可以直接跳过,所以这两个成员函数主要负责在Data Block读取中跳过一些不需要读取的数据信息;

  HandleTable
  这个类和ObjectOutputStream类中的HandleTable同样对应。但是这个类的作用不一样:
  该类为一个不同步的表结构,它用于跟踪指向对象的句柄这种映射关系,以及和反序列化对象相关联的ClassNotFoundException异常。这个类的实现中使用了异常传播算法,它用于判断哪一个类在反序列化过程抛出了ClassNotFoundException异常,将这些异常信息放入对象图中连续或者不连续的生命周期内。这个类通常的使用方式如下:
  在反序列化过程中,一个对象第一次调用assign方法赋值给一个引用Handle,这个方法将会把赋值过的引用Handle设置成“open”状态,其中如果依赖其他异常信息的引用Handle能够通过调用markDependency方法来标记注册,或者调用markException直接将该Handle和异常联系起来。如果一个Handle标记了一个异常,当前HandleTable将重新负责异常的传播,它会将异常信息传播给所有依赖当前对象的对象;
  一旦Handle引用的所有的异常信息或者依赖都被注册过后,这个引用Handle就会调用finish方法设置成“close”状态。完成一个引用Handle的行为将允许异常传播算法积极地去剪断依赖链接,其目的是为了减少性能和内存的开销;
  *:异常传播算法依赖于Handle,因为它的assigned/finished操作都使用的LIFO顺序,这样更加简单;尽管如此,它不能够强制执行这些约束;
  静态属性
        private static final byte STATUS_OK = 1;
        private static final byte STATUS_UNKNOWN = 2;
        private static final byte STATUS_EXCEPTION = 3;
  读取过程中数据的状态信息
  • STATUS_OK
    表示当前读取的数据是合法数据
  • STATUS_UNKNOWN
    表示当前读取的对象数据为“Unknown”对象,即无法识别
  • STATUS_EXCEPTION
    表示当前读取数据的过程出现了异常信息
  成员函数
        void markDependency(int dependent, int target) {
            if (dependent == NULL_HANDLE || target == NULL_HANDLE) {
                return;
            }
            switch (status[dependent]) {

                case STATUS_UNKNOWN:
                    switch (status[target]) {
                        case STATUS_OK:
                            // ignore dependencies on objs with no exception
                            break;

                        case STATUS_EXCEPTION:
                            // eagerly propagate exception
                            markException(dependent,
                                (ClassNotFoundException) entries[target]);
                            break;

                        case STATUS_UNKNOWN:
                            // add to dependency list of target
                            if (deps[target] == null) {
                                deps[target] = new HandleList();
                            }
                            deps[target].add(dependent);

                            // remember lowest unresolved target seen
                            if (lowDep < 0 || lowDep > target) {
                                lowDep = target;
                            }
                            break;

                        default:
                            throw new InternalError();
                    }
                    break;

                case STATUS_EXCEPTION:
                    break;

                default:
                    throw new InternalError();
            }
        }

        void markException(int handle, ClassNotFoundException ex) {
            switch (status[handle]) {
                case STATUS_UNKNOWN:
                    status[handle] = STATUS_EXCEPTION;
                    entries[handle] = ex;

                    // propagate exception to dependents
                    HandleList dlist = deps[handle];
                    if (dlist != null) {
                        int ndeps = dlist.size();
                        for (int i = 0; i < ndeps; i++) {
                            markException(dlist.get(i), ex);
                        }
                        deps[handle] = null;
                    }
                    break;

                case STATUS_EXCEPTION:
                    break;

                default:
                    throw new InternalError();
            }
        }
  这两个方法为上边提到的markDependencymarkException方法,本文不探讨它的细节;
  PeekInputStream
  这个类是ObjectOutputStream中没有对应的类,它使得输入流可支持栈顶单字节读取操作,其定义如下:
    private static class PeekInputStream extends InputStream {

        /** underlying stream */
        private final InputStream in;
        /** peeked byte */
        private int peekb = -1;

        /**
         * Creates new PeekInputStream on top of given underlying stream.
         */
        PeekInputStream(InputStream in) {
            this.in = in;
        }

        /**
         * Peeks at next byte value in stream.  Similar to read(), except
         * that it does not consume the read value.
         */
        int peek() throws IOException {
            return (peekb >= 0) ? peekb : (peekb = in.read());
        }

        public int read() throws IOException {
            if (peekb >= 0) {
                int v = peekb;
                peekb = -1;
                return v;
            } else {
                return in.read();
            }
        }

        public int read(byte[] b, int off, int len) throws IOException {
            if (len == 0) {
                return 0;
            } else if (peekb < 0) {
                return in.read(b, off, len);
            } else {
                b[off++] = (byte) peekb;
                len--;
                peekb = -1;
                int n = in.read(b, off, len);
                return (n >= 0) ? (n + 1) : 1;
            }
        }

        void readFully(byte[] b, int off, int len) throws IOException {
            int n = 0;
            while (n < len) {
                int count = read(b, off + n, len - n);
                if (count < 0) {
                    throw new EOFException();
                }
                n += count;
            }
        }

        public long skip(long n) throws IOException {
            if (n <= 0) {
                return 0;
            }
            int skipped = 0;
            if (peekb >= 0) {
                peekb = -1;
                skipped++;
                n--;
            }
            return skipped + skip(n);
        }

        public int available() throws IOException {
            return in.available() + ((peekb >= 0) ? 1 : 0);
        }

        public void close() throws IOException {
            in.close();
        }
    }
  ValidationList
  该类在ObjectOutputStream中也没有对应的定义,它主要负责处理反序列化完成过后的回调信息
    private static class ValidationList {

        private static class Callback {
            final ObjectInputValidation obj;
            final int priority;
            Callback next;
            final AccessControlContext acc;

            Callback(ObjectInputValidation obj, int priority, Callback next,
                AccessControlContext acc)
            {
                this.obj = obj;
                this.priority = priority;
                this.next = next;
                this.acc = acc;
            }
        }

        private Callback list;

        ValidationList() {
        }

        void register(ObjectInputValidation obj, int priority)
            throws InvalidObjectException
        {
            if (obj == null) {
                throw new InvalidObjectException("null callback");
            }

            Callback prev = null, cur = list;
            while (cur != null && priority < cur.priority) {
                prev = cur;
                cur = cur.next;
            }
            AccessControlContext acc = AccessController.getContext();
            if (prev != null) {
                prev.next = new Callback(obj, priority, cur, acc);
            } else {
                list = new Callback(obj, priority, list, acc);
            }
        }

        void doCallbacks() throws InvalidObjectException {
            try {
                while (list != null) {
                    AccessController.doPrivileged(
                        new PrivilegedExceptionAction()
                    {
                        public Void run() throws InvalidObjectException {
                            list.obj.validateObject();
                            return null;
                        }
                    }, list.acc);
                    list = list.next;
                }
            } catch (PrivilegedActionException ex) {
                list = null;
                throw (InvalidObjectException) ex.getException();
            }
        }

        public void clear() {
            list = null;
        }
    }
  从ObjectInputStream的类定义可以知道,反序列化过程中没有调试辅助信息代码段,它没有定义类似java.io.ObjectOutputStream.DebugTraceInfoStack的类用来作为调试的辅助工具类;而反序列化的内部类定义了新的结构ValidationList用于做反序列化过后的回调执行;它同样没有ReplaceTable来保存“替换”对象的信息——实际上反序列化中不需要保存“替换”对象的信息,因为执行反序列化的时候它会根据对象的性质直接将这个对象“Resolve”并且还原成Java对象或者Java引用,对它而言也没有必要使用ReplaceTable来存储“替换”对象的信息;
  3)静态成员:
  和ObjectOutputStream不太一样的是ObjectInputStream中多了静态成员
    /** handle value representing null */
    private static final int NULL_HANDLE = -1;

    /** marker for unshared objects in internal handle table */
    private static final Object unsharedMarker = new Object();

    /** table mapping primitive type names to corresponding class objects */
    private static final HashMap> primClasses
        = new HashMap<>(8, 1.0F);
  这些静态成员的解析如下:
  • NULL_HANDLE
    该成员表示null值,因为读取过程引用Handle是一个整数,所以用整数中的-1表示null引用;
  • unsharedMarker
    该对象主要用于在内部的“引用哈希表”中标记“unshared”状态的对象——“unshared”状态的对象的重建步骤和“unshared”不一样
  • primClasses
    该哈希表用于存储基础类型名称到对应的类实例的映射,它在静态初始化块中会被初始化:

        static {
            primClasses.put("boolean", boolean.class);
            primClasses.put("byte", byte.class);
            primClasses.put("char", char.class);
            primClasses.put("short", short.class);
            primClasses.put("int", int.class);
            primClasses.put("long", long.class);
            primClasses.put("float", float.class);
            primClasses.put("double", double.class);
            primClasses.put("void", void.class);
        }
  4)成员属性:
  ObjectInputStream中的成员属性ObjectOutputStream中的大部分都是对应的,先看看它的定义内容:
    /** filter stream for handling block data conversion */
    private final BlockDataInputStream bin;
    /** validation callback list */
    private final ValidationList vlist;
    /** recursion depth */
    private int depth;
    /** whether stream is closed */
    private boolean closed;

    /** wire handle -> obj/exception map */
    private final HandleTable handles;
    /** scratch field for passing handle values up/down call stack */
    private int passHandle = NULL_HANDLE;
    /** flag set when at end of field value block with no TC_ENDBLOCKDATA */
    private boolean defaultDataEnd = false;

    /** buffer for reading primitive field values */
    private byte[] primVals;

    /** if true, invoke readObjectOverride() instead of readObject() */
    private final boolean enableOverride;
    /** if true, invoke resolveObject() */
    private boolean enableResolve;

    /**
     * Context during upcalls to class-defined readObject methods; holds
     * object currently being deserialized and descriptor for current class.
     * Null when not during readObject upcall.
     */
    private SerialCallbackContext curContext;
  这里保留了注释,让读者可以理解每一个属性的作用,讲几个在ObjectInputStream中具有特殊作用的成员属性,其他的读者可以和ObjectOutputStream中的成员属性对比分析:
  • vlist——java.io.ObjectInputStream.ValidationList
    该成员属性的概念类型为一个“验证链”,它主要用于提供一个回调操作验证集合
  • closed——boolean
    判断当前字节流是否已经处于“closed”状态,如果已经是“closed”了,则不需要再从字节流中读取数据;
  • defaultDataEnd——boolean
    该标记表示一个数据段的结束,针对Data Block模式的数据段而言它的结束会出现一个TC_ENDBLOCKDATA标记,该成员属性是用于判断非Data Block模式的数据段的结束——这种情况下无法读取TC_ENDBLOCKDATA来判断;
  • primVals——byte[]
    该成员属性表示从字节流中读取的所有对象中基础类型的字段值的集合,前文分析二进制序列时已经知道,对象中的成员属性值是直接输出的,它没有任何标记来识别,而primVals成员属性中就存储了所有基础类型的成员属性的值的集合;
  5)本地方法:
  同ObjectOutputStream一样,在ObjectInputStream中也定义了本地方法,只是它的本地方法ObjectOutputStream多了一个:
    /**
     * Converts specified span of bytes into float values.
     */
    // REMIND: remove once hotspot inlines Float.intBitsToFloat
    private static native void bytesToFloats(byte[] src, int srcpos,
                                             float[] dst, int dstpos,
                                             int nfloats);

    /**
     * Converts specified span of bytes into double values.
     */
    // REMIND: remove once hotspot inlines Double.longBitsToDouble
    private static native void bytesToDoubles(byte[] src, int srcpos,
                                              double[] dst, int dstpos,
                                              int ndoubles);

    /**
     * Returns the first non-null class loader (not counting class loaders of
     * generated reflection implementation classes) up the execution stack, or
     * null if only code from the null class loader is on the stack.  This
     * method is also called via reflection by the following RMI-IIOP class:
     *
     *     com.sun.corba.se.internal.util.JDKClassLoader
     *
     * This method should not be removed or its signature changed without
     * corresponding modifications to the above class.
     */
    // REMIND: change name to something more accurate?
    private static native ClassLoader latestUserDefinedLoader();
  除了针对浮点数读取的本地方法以外,它还包含了一个latestUserDefinedLoader方法,该方法主要用于返回系统中第一个null值的类加载器,这个类加载器位于执行堆栈的栈顶。除了使用native本地方法可以获得该类加载器以外,还可以通过RMI-IIOP中的反射方法获得:com.sun.corba.se.internal.util.JDKClassLoader。JVM在反序列化Java对象时,它会在ClassLoader中重建Java对象,对于同一个包中的Java对象,如果使用的类加载器不同,则也会抛出ClassNotFoundException异常信息——除了ClassLoader无法找到系统中的类会抛出该异常,如果使用不同的ClassLoader加载的类相互调用时也会抛出该异常。所以该方法设计的目的应该是使得所有重建Java对象的类加载器保持使用统一的类加载器,这样不会因为使用了不同的ClassLoader而抛出该异常。
  6)DataInput接口
  DataInput接口是ObjectInput接口的父接口,而ObjectInputStream实现了ObjectInput接口,先看看DataInput接口中的方法定义:

  这些接口方法在ObjectInputStream中的代码实现如下:
    public boolean readBoolean() throws IOException {
        return bin.readBoolean();
    }
    public byte readByte() throws IOException  {
        return bin.readByte();
    }
    public int readUnsignedByte()  throws IOException {
        return bin.readUnsignedByte();
    }
    public char readChar()  throws IOException {
        return bin.readChar();
    }
    public short readShort()  throws IOException {
        return bin.readShort();
    }
    public int readUnsignedShort() throws IOException {
        return bin.readUnsignedShort();
    }
    public int readInt()  throws IOException {
        return bin.readInt();
    }
    public long readLong()  throws IOException {
        return bin.readLong();
    }
    public float readFloat() throws IOException {
        return bin.readFloat();
    }
    public double readDouble() throws IOException {
        return bin.readDouble();
    }
    public void readFully(byte[] buf) throws IOException {
        bin.readFully(buf, 0, buf.length, false);
    }
    public void readFully(byte[] buf, int off, int len) throws IOException {
        int endoff = off + len;
        if (off < 0 || len < 0 || endoff > buf.length || endoff < 0) {
            throw new IndexOutOfBoundsException();
        }
        bin.readFully(buf, off, len, false);
    }
    public int skipBytes(int len) throws IOException {
        return bin.skipBytes(len);
    }
    @Deprecated
    public String readLine() throws IOException {
        return bin.readLine();
    }
    public String readUTF() throws IOException {
        return bin.readUTF();
    }
  实际上和ObjectOutputStream中定义的“隐藏字节流【underlying stream】”一样,ObjectInputStream在实现这些read*方法时调用了binjava.io.ObjectInputStream.BlockDataInputStream成员函数中的方法;
  7)ObjectInput接口
  接下来再看看ObjectInput接口中的方法实现,其整体定义如下:

  ObjectInputStream类中实现了这些方法,先保留readObject方法,看看其他几个方法:
    public int available() throws IOException {
        return bin.available();
    }

    public void close() throws IOException {
        /*
         * Even if stream already closed, propagate redundant close to
         * underlying stream to stay consistent with previous implementations.
         */
        closed = true;
        if (depth == 0) {
            clear();
        }
        bin.close();
    }
  需要注意一点:skip方法因为已经在父类InputStream中提供了默认实现,所以ObjectInputStream中并没有提供skip方法的实现代码;
  【*:ObjectOutputStream中提供了关于流协议的方法,但是在ObjectInputStream方法中,并没有提供任何流协议相关的方法。】
  8)readFields
  该方法和putFields方法对应,而且ObjectInputStream类中没有和writeFields对应的方法,它的代码定义如下:
    public ObjectInputStream.GetField readFields()
        throws IOException, ClassNotFoundException
    {
        if (curContext == null) {
            throw new NotActiveException("not in call to readObject");
        }
        Object curObj = curContext.getObj();
        ObjectStreamClass curDesc = curContext.getDesc();
        bin.setBlockDataMode(false);
        GetFieldImpl getField = new GetFieldImpl(curDesc);
        getField.readFields();
        bin.setBlockDataMode(true);
        if (!curDesc.hasWriteObjectData()) {
            /*
             * Fix for 4360508: since stream does not contain terminating
             * TC_ENDBLOCKDATA tag, set flag so that reading code elsewhere
             * knows to simulate end-of-custom-data behavior.
             */
            defaultDataEnd = true;
        }

        return getField;
    }
  这个方法用于读取类定义中的字段信息,注意——该方法的调用必须在readObject方法内调用,不能在其他位置调用;从代码注释中可以知道:其中有一段代码是为了处理4360508这个Bug而存在的;
  9)read*单元方法
  接下来看看ObjectInputStream类中read*私有单元方法,这些方法主要用于读取TC_*标记段,这些方法在读取字节时都会判断读取的第一个字节是否对应区段的TC_*标记的值,如果这个值不匹配则抛出java.lang.InternalError信息;
  readFataException
    private IOException readFatalException() throws IOException {
        if (bin.readByte() != TC_EXCEPTION) {
            throw new InternalError();
        }
        clear();
        return (IOException) readObject0(false);
    }
  该方法主要用于读取TC_EXCEPTION标记段,这个方法的实现和writeFataException方法是完全对应的;
  readNull
    private Object readNull() throws IOException {
        if (bin.readByte() != TC_NULL) {
            throw new InternalError();
        }
        passHandle = NULL_HANDLE;
        return null;
    }
  从字节流中读取一个TC_NULL标记段,如果是引用Handle则读取一个NULL_HANDLE
  readHandle
    private Object readHandle(boolean unshared) throws IOException {
        if (bin.readByte() != TC_REFERENCE) {
            throw new InternalError();
        }
        passHandle = bin.readInt() - baseWireHandle;
        if (passHandle < 0 || passHandle >= handles.size()) {
            throw new StreamCorruptedException(
                String.format("invalid handle value: %08X", passHandle +
                baseWireHandle));
        }
        if (unshared) {
            // REMIND: what type of exception to throw here?
            throw new InvalidObjectException(
                "cannot read back reference as unshared");
        }

        Object obj = handles.lookupObject(passHandle);
        if (obj == unsharedMarker) {
            // REMIND: what type of exception to throw here?
            throw new InvalidObjectException(
                "cannot read back reference to unshared object");
        }
        return obj;
    }
  该方法从字节流中读取TC_REFERENCE标记段,它会把读取的引用Handle赋值给passHandle变量。注意该方法会抛出三个异常信息除开InternalError】
  • java.io.StreamCorruptedException
    如果passHandle的值大于handles中的尺寸,或者通过和baseWireHandle计算过后的值小于0,则抛出该异常信息
  • java.io.InvalidObjectException
    如果调用readHandle方法时使用了“unshared”的方法则抛出该异常信息
  • java.io.InvalidObjectException
    如果从handles中获取的对象和unsharedMarker引用的对象是同一个对象则抛出该异常信息
  readClass
    private Class readClass(boolean unshared) throws IOException {
        if (bin.readByte() != TC_CLASS) {
            throw new InternalError();
        }
        ObjectStreamClass desc = readClassDesc(false);
        Class cl = desc.forClass();
        passHandle = handles.assign(unshared ? unsharedMarker : cl);

        ClassNotFoundException resolveEx = desc.getResolveException();
        if (resolveEx != null) {
            handles.markException(passHandle, resolveEx);
        }

        handles.finish(passHandle);
        return cl;
    }
  该方法从字节流中读取TC_CLASS标记段的信息;
  readProxyDesc
    private ObjectStreamClass readProxyDesc(boolean unshared)
        throws IOException
    {
        if (bin.readByte() != TC_PROXYCLASSDESC) {
            throw new InternalError();
        }

        ObjectStreamClass desc = new ObjectStreamClass();
        int descHandle = handles.assign(unshared ? unsharedMarker : desc);
        passHandle = NULL_HANDLE;

        int numIfaces = bin.readInt();
        String[] ifaces = new String[numIfaces];
        for (int i = 0; i < numIfaces; i++) {
            ifaces[i] = bin.readUTF();
        }

        Class cl = null;
        ClassNotFoundException resolveEx = null;
        bin.setBlockDataMode(true);
        try {
            if ((cl = resolveProxyClass(ifaces)) == null) {
                resolveEx = new ClassNotFoundException("null class");
            }
        } catch (ClassNotFoundException ex) {
            resolveEx = ex;
        }
        skipCustomData();

        desc.initProxy(cl, resolveEx, readClassDesc(false));

        handles.finish(descHandle);
        passHandle = descHandle;
        return desc;
    }
  该方法从字节流中读取TC_PROXYCLASSDESC标记段的信息,该方法的流程如下:
  1. 先判断TC_PROXYCLASSDESC标记看是否需要抛出InternalError
  2. 如果使用的读取模式是共享模式,则将从引用->对象的映射中读取一个新的desc,否则直接读取unsharedMarker中读取对应的对象;
  3. 构建接口名称数组ifaces
  4. 开启Data Block模式然后调用resolveProxyClass方法处理每一个接口内容;
  5. 调用skipCustomData方法跳过自定义信息的读取;
  6. 调用ObjectStreamClass中的initProxy方法来初始化cl
  7. 调用handlesfinish方法完成引用Handle的赋值操作,将结果赋值给passHandle成员属性
  readNonProxyDesc
    private ObjectStreamClass readNonProxyDesc(boolean unshared)
        throws IOException
    {
        if (bin.readByte() != TC_CLASSDESC) {
            throw new InternalError();
        }

        ObjectStreamClass desc = new ObjectStreamClass();
        int descHandle = handles.assign(unshared ? unsharedMarker : desc);
        passHandle = NULL_HANDLE;

        ObjectStreamClass readDesc = null;
        try {
            readDesc = readClassDescriptor();
        } catch (ClassNotFoundException ex) {
            throw (IOException) new InvalidClassException(
                "failed to read class descriptor").initCause(ex);
        }

        Class cl = null;
        ClassNotFoundException resolveEx = null;
        bin.setBlockDataMode(true);
        try {
            if ((cl = resolveClass(readDesc)) == null) {
                resolveEx = new ClassNotFoundException("null class");
            }
        } catch (ClassNotFoundException ex) {
            resolveEx = ex;
        }
        skipCustomData();

        desc.initNonProxy(readDesc, cl, resolveEx, readClassDesc(false));

        handles.finish(descHandle);
        passHandle = descHandle;
        return desc;
    }
  该方法从字节流中读取TC_CLASSDESC标记段的信息,该方法的流程如下:
  1. 先判断TC_CLASSDESC标记看是否需要抛出InternalError
  2. 如果使用的读取模式是共享模式,则将从引用->对象的映射中读取一个新的desc,否则直接读取unsharedMarker中读取对应的对象;
  3. 调用readClassDescriptor方法读取当前类的元数据信息
  4. 开启Data Block模式然后调用resolveClass方法处理当前类的信息;
  5. 调用skipCustomData方法跳过自定义信息的读取;
  6. 调用ObjectStreamClass中的initNonProxy方法初始化cl
  7. 调用handlesfinish方法完成引用Handle的赋值操作,将结果赋值给passHandle成员属性
  readString
    private String readString(boolean unshared) throws IOException {
        String str;
        byte tc = bin.readByte();
        switch (tc) {
            case TC_STRING:
                str = bin.readUTF();
                break;

            case TC_LONGSTRING:
                str = bin.readLongUTF();
                break;

            default:
                throw new StreamCorruptedException(
                    String.format("invalid type code: %02X", tc));
        }
        passHandle = handles.assign(unshared ? unsharedMarker : str);
        handles.finish(passHandle);
        return str;
    }
  该方法从字节流中读取TC_STRINGTC_LONGSTRING标记段信息,该方法的流程如下:
  1. 先从系统中读取字节标记:
    如果标记值为TC_STRING,则调用readUTF方法读取String信息;
    如果标记值为TC_LONGSTRING,则调用readLongUTF方法读取String信息;
    如果两个标记不是则抛出java.io.StreamCorruptedException异常信息;
  2. 调用handlesassign方法完成引用Handle的赋值操作,将结果赋值给passHandle成员属性,然后调用finish方法;
  readArray
    private Object readArray(boolean unshared) throws IOException {
        if (bin.readByte() != TC_ARRAY) {
            throw new InternalError();
        }

        ObjectStreamClass desc = readClassDesc(false);
        int len = bin.readInt();

        Object array = null;
        Class cl, ccl = null;
        if ((cl = desc.forClass()) != null) {
            ccl = cl.getComponentType();
            array = Array.newInstance(ccl, len);
        }

        int arrayHandle = handles.assign(unshared ? unsharedMarker : array);
        ClassNotFoundException resolveEx = desc.getResolveException();
        if (resolveEx != null) {
            handles.markException(arrayHandle, resolveEx);
        }

        if (ccl == null) {
            for (int i = 0; i < len; i++) {
                readObject0(false);
            }
        } else if (ccl.isPrimitive()) {
            if (ccl == Integer.TYPE) {
                bin.readInts((int[]) array, 0, len);
            } else if (ccl == Byte.TYPE) {
                bin.readFully((byte[]) array, 0, len, true);
            } else if (ccl == Long.TYPE) {
                bin.readLongs((long[]) array, 0, len);
            } else if (ccl == Float.TYPE) {
                bin.readFloats((float[]) array, 0, len);
            } else if (ccl == Double.TYPE) {
                bin.readDoubles((double[]) array, 0, len);
            } else if (ccl == Short.TYPE) {
                bin.readShorts((short[]) array, 0, len);
            } else if (ccl == Character.TYPE) {
                bin.readChars((char[]) array, 0, len);
            } else if (ccl == Boolean.TYPE) {
                bin.readBooleans((boolean[]) array, 0, len);
            } else {
                throw new InternalError();
            }
        } else {
            Object[] oa = (Object[]) array;
            for (int i = 0; i < len; i++) {
                oa[i] = readObject0(false);
                handles.markDependency(arrayHandle, passHandle);
            }
        }

        handles.finish(arrayHandle);
        passHandle = arrayHandle;
        return array;
    }
  该方法从字节流中读取TC_ARRAY标记段,其详细流程如下:
  1. 先从系统中读取TC_ARRAY标记,判断是否抛出InternalError
  2. 读取Array数组的类描述信息【元数据信息
  3. 然后读取Array数组的长度
  4. 从系统中读取Array数组中元素的类型信息
  5. handles成员属性中得到当前数组的整数引用Handle信息;
  6. 调用ObjectStreamClass中的getResolveException获取“Resolve”过程中的异常信息,如果出现异常则调用markException方法标记;
  7. 如果Array数组中的元素是基础类型则执行基础类型的反序列化操作,若获取的类型为null则调用readObject0方法反序列化该对象,否则先调用readObject0方法反序列化该对象,然后调用markDependency标记依赖;
  8. 调用handlesfinish方法完成引用Handle的赋值操作,将结果赋值给passHandle成员属性
  readEnum
    private Enum readEnum(boolean unshared) throws IOException {
        if (bin.readByte() != TC_ENUM) {
            throw new InternalError();
        }

        ObjectStreamClass desc = readClassDesc(false);
        if (!desc.isEnum()) {
            throw new InvalidClassException("non-enum class: " + desc);
        }

        int enumHandle = handles.assign(unshared ? unsharedMarker : null);
        ClassNotFoundException resolveEx = desc.getResolveException();
        if (resolveEx != null) {
            handles.markException(enumHandle, resolveEx);
        }

        String name = readString(false);
        Enum en = null;
        Class cl = desc.forClass();
        if (cl != null) {
            try {
                en = Enum.valueOf(cl, name);
            } catch (IllegalArgumentException ex) {
                throw (IOException) new InvalidObjectException(
                    "enum constant " + name + " does not exist in " +
                    cl).initCause(ex);
            }
            if (!unshared) {
                handles.setObject(enumHandle, en);
            }
        }

        handles.finish(enumHandle);
        passHandle = enumHandle;
        return en;
    }
  该方法从字节流中读取TC_ENUM标记段,其详细流程如下:
  1. 从系统中读取TC_ENUM标记,判断是否抛出InternalError
  2. 从系统中读取Enum类型的类描述信息,如果发现类型不为Enum则抛出java.io.InvalidClassException异常;
  3. handles成员属性中得到当前数组的整数引用Handle信息;
  4. 调用ObjectStreamClass中的getResolveException获取“Resolve”过程中的异常信息,如果出现异常则调用markException方法标记;
  5. 从系统中读取枚举常量字符串字面量【调用name()方法得到的字符串】
  6. 实例化读取到的Enum类型;
  7. 调用handlesfinish方法完成引用Handle的赋值操作,将结果赋值给passHandle成员属性
  readOridinaryObject
    private Object readOrdinaryObject(boolean unshared)
        throws IOException
    {
        if (bin.readByte() != TC_OBJECT) {
            throw new InternalError();
        }

        ObjectStreamClass desc = readClassDesc(false);
        desc.checkDeserialize();

        Object obj;
        try {
            obj = desc.isInstantiable() ? desc.newInstance() : null;
        } catch (Exception ex) {
            throw (IOException) new InvalidClassException(
                desc.forClass().getName(),
                "unable to create instance").initCause(ex);
        }

        passHandle = handles.assign(unshared ? unsharedMarker : obj);
        ClassNotFoundException resolveEx = desc.getResolveException();
        if (resolveEx != null) {
            handles.markException(passHandle, resolveEx);
        }

        if (desc.isExternalizable()) {
            readExternalData((Externalizable) obj, desc);
        } else {
            readSerialData(obj, desc);
        }

        handles.finish(passHandle);

        if (obj != null &&
            handles.lookupException(passHandle) == null &&
            desc.hasReadResolveMethod())
        {
            Object rep = desc.invokeReadResolve(obj);
            if (unshared && rep.getClass().isArray()) {
                rep = cloneArray(rep);
            }
            if (rep != obj) {
                handles.setObject(passHandle, obj = rep);
            }
        }

        return obj;
    }
  该方法从字节流中读取TC_OBJECT标记段,其详细流程如下:
  1. 从系统中读取TC_OBJECT标记,判断是否抛出InternalError
  2. 检查当前处理的对象是否是一个可反序列化对象【即检查它是否具有“可序列化的语义”】
  3. 从系统中读取当前Java对象所属类的描述信息【类元数据信息
  4. 调用handlesfinish方法完成引用Handle的赋值操作,将结果赋值给passHandle成员属性
  5. 调用ObjectStreamClass中的getResolveException获取“Resolve”过程中的异常信息,如果出现异常则调用markException方法标记;
  6. 如果当前对象实现了Externalizable接口则调用readExternalData将对象数据写入该对象,若它实现的是Serializable接口则调用readSerialData方法执行反序列化
  7. 调用handlesfinish方法完成引用Handle的赋值操作;
  8. 判断读取对象是否实现了readResolve方法,如果实现了该方法并且引用Handle没指向一个异常信息且获得的对象不为null时,则调用该对象的readResolve方法;
  10)其他read*单元方法
  readExternalData
    private void readExternalData(Externalizable obj, ObjectStreamClass desc)
        throws IOException
    {
        SerialCallbackContext oldContext = curContext;
        curContext = null;
        try {
            boolean blocked = desc.hasBlockExternalData();
            if (blocked) {
                bin.setBlockDataMode(true);
            }
            if (obj != null) {
                try {
                    obj.readExternal(this);
                } catch (ClassNotFoundException ex) {
                    /*
                     * In most cases, the handle table has already propagated
                     * a CNFException to passHandle at this point; this mark
                     * call is included to address cases where the readExternal
                     * method has cons'ed and thrown a new CNFException of its
                     * own.
                     */
                     handles.markException(passHandle, ex);
                }
            }
            if (blocked) {
                skipCustomData();
            }
        } finally {
            curContext = oldContext;
        }
        /*
         * At this point, if the externalizable data was not written in
         * block-data form and either the externalizable class doesn't exist
         * locally (i.e., obj == null) or readExternal() just threw a
         * CNFException, then the stream is probably in an inconsistent state,
         * since some (or all) of the externalizable data may not have been
         * consumed.  Since there's no "correct" action to take in this case,
         * we mimic the behavior of past serialization implementations and
         * blindly hope that the stream is in sync; if it isn't and additional
         * externalizable data remains in the stream, a subsequent read will
         * most likely throw a StreamCorruptedException.
         */
    }
  该方法的代码中保留了注释,实际上这个方法在系统得知反序列化的对象实现的是外部化而不是序列化时调用,它的详细流程如下:
  1. ObjectOutputStream中的writeExternal方法一样,它的调用需要将上下文环境变量curContext设置成null,调用结束后再还原;
  2. 先查看读取的字节流中是否包含了外部化Data Block模式写入的数据,这个检测通过调用ObjectStreamClasshasBlockExternalData方法完成,如果包含则先启用Data Block模式
  3. 若传入的对象不为null,则调用对象本身的readExternal方法【外部化的类必须实现该方法】
  4. readExternal方法调用完成过后,如果字节流中使用的模式是Data Block模式则直接跳过自定义数据段的读取;
  5. 异常部分的注释不解释了,这里看看最后这部分的注释:如果外部化的数据并没有以Data Block的形式写入字节流或者外部化的类并不存在于本地JVM环境,又或者readExternal方法引起了CNFException异常,当前字节流会因为外部化的数据并没有处理而处于“inconsistent【不一致】”的状态。如果环境中不存在“correct【修复】”行为,系统将模仿序列化实现的行为并且盲目地系统该字节流会同步数据而一致,如果字节流中并没有外部化数据,则随后的调用将会抛出java.io.StreamCorruptedException异常信息;
  readSerialData
    private void readSerialData(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 (slots[i].hasData) {
                if (obj != null &&
                    slotDesc.hasReadObjectMethod() &&
                    handles.lookupException(passHandle) == null)
                {
                    SerialCallbackContext oldContext = curContext;

                    try {
                        curContext = new SerialCallbackContext(obj, slotDesc);

                        bin.setBlockDataMode(true);
                        slotDesc.invokeReadObject(obj, this);
                    } catch (ClassNotFoundException ex) {
                        /*
                         * In most cases, the handle table has already
                         * propagated a CNFException to passHandle at this
                         * point; this mark call is included to address cases
                         * where the custom readObject method has cons'ed and
                         * thrown a new CNFException of its own.
                         */
                        handles.markException(passHandle, ex);
                    } finally {
                        curContext.setUsed();
                        curContext = oldContext;
                    }

                    /*
                     * defaultDataEnd may have been set indirectly by custom
                     * readObject() method when calling defaultReadObject() or
                     * readFields(); clear it to restore normal read behavior.
                     */
                    defaultDataEnd = false;
                } else {
                    defaultReadFields(obj, slotDesc);
                }
                if (slotDesc.hasWriteObjectData()) {
                    skipCustomData();
                } else {
                    bin.setBlockDataMode(false);
                }
            } else {
                if (obj != null &&
                    slotDesc.hasReadObjectNoDataMethod() &&
                    handles.lookupException(passHandle) == null)
                {
                    slotDesc.invokeReadObjectNoData(obj);
                }
            }
        }
    }
  该方法在系统得知其反序列化对象具有“可序列化”的语义时将反序列化对象的相关信息,它的详细流程如下:
  1. 反序列化该对象之前,先从类描述信息【元数据信息中获取ClassDataSlot的信息;
  2. ClassDataSlot成员属性hasData标记有数据,则判断该反序列化对象是否重写了readObject方法
    a.如果重写了该方法,则开启Data Block模式,再调用该对象的readObject方法,调用过后关闭Data Block模式
    b.如果没有重写该方法,则直接调用defaultReadFields方法读取该类中的每一个字段的值;
  3. 如果ObjectOutputStream类使用writeObject方法写入了额外的自定义信息,则调用skipCustomData方法跳过读取,否则直接关闭Data Block模式
  4. 如果ClassDataSlot成员属性hasData标记没有序列化数据,则调用invokeReadObjectNoData方法来处理当前对象;
  11)两个default
  ObjectInputStream和前文中的ObjectOutputStream一样定义了两个default的默认方法,用于默认反序列化。
  defaultReadObject
    public void defaultReadObject()
        throws IOException, ClassNotFoundException
    {
        if (curContext == null) {
            throw new NotActiveException("not in call to readObject");
        }
        Object curObj = curContext.getObj();
        ObjectStreamClass curDesc = curContext.getDesc();
        bin.setBlockDataMode(false);
        defaultReadFields(curObj, curDesc);
        bin.setBlockDataMode(true);
        if (!curDesc.hasWriteObjectData()) {
            /*
             * Fix for 4360508: since stream does not contain terminating
             * TC_ENDBLOCKDATA tag, set flag so that reading code elsewhere
             * knows to simulate end-of-custom-data behavior.
             */
            defaultDataEnd = true;
        }
        ClassNotFoundException ex = handles.lookupException(passHandle);
        if (ex != null) {
            throw ex;
        }
    }
  该方法负责读取需要反序列化的对象,并且构建该对象信息;看看这个方法的详细流程:
  1. 这个方法只能在readObject方法内部调用,所以会检查curContext环境,如果上下文调用环境不对则抛出NotActiveException异常信息
  2. 从上下文环境获取对象以及对象所属类的类描述信息
  3. 关闭Data Block模式,并且调用defaultReadFields方法读取该类的字段信息,调用完成过后再开启Data Block模式
  4. 检查对象是否重写了writeObject方法,如果没有重写该方法需要手动设置defaultDataEndtrue,从注释可以知道这段代码的目的是为了修复4360508的Bug而存在;
  5. 最后使用handles查找是否出现了ClassNotFoundException异常,如果出现该异常则表示对象重建过程失败,抛出该异常信息;
  defaultReadFields
    private void defaultReadFields(Object obj, ObjectStreamClass desc)
        throws IOException
    {
        // REMIND: is isInstance check necessary?
        Class cl = desc.forClass();
        if (cl != null && obj != null && !cl.isInstance(obj)) {
            throw new ClassCastException();
        }

        int primDataSize = desc.getPrimDataSize();
        if (primVals == null || primVals.length < primDataSize) {
            primVals = new byte[primDataSize];
        }
        bin.readFully(primVals, 0, primDataSize, false);
        if (obj != null) {
            desc.setPrimFieldValues(obj, primVals);
        }

        int objHandle = passHandle;
        ObjectStreamField[] fields = desc.getFields(false);
        Object[] objVals = new Object[desc.getNumObjFields()];
        int numPrimFields = fields.length - objVals.length;
        for (int i = 0; i < objVals.length; i++) {
            ObjectStreamField f = fields[numPrimFields + i];
            objVals[i] = readObject0(f.isUnshared());
            if (f.getField() != null) {
                handles.markDependency(objHandle, passHandle);
            }
        }
        if (obj != null) {
            desc.setObjFieldValues(obj, objVals);
        }
        passHandle = objHandle;
    }
  该方法根据传入的对象以及对象的类描述信息读取字段数据【成员属性相关信息;看看这个方法的详细流程:
  1. 先从传入的desc中获得类描述信息,如果传入对象和类不匹配则抛出java.lang.ClassCastException异常信息;
  2. 然后从desc中读取基本类型字段信息,并且读取基本字段的信息;
  3. 最后读取对象类型的字段信息,调用readObject0方法读取这些字段数据;
  4. 在读取过程中如果从ObjectStreamField得到的字段信息不为空还需要调用markDependency方法标记依赖关系;
  5. 若对象信息不为空则设置其获取到的所有字段【基础类型和对象类型的值;
  12)read*控制方法
  除了上边提到的defaultread*单元方法以外,ObjectInputStream类中还包含了许多read*方法,这些read*方法主要用于不同的情况对Java对象进行重建恢复操作,它属于read*方法的上层代码,一般控制了代码执行流程,并不提供和对象以及类相关的实质性代码;
  readClassDescriptor
    protected ObjectStreamClass readClassDescriptor()
        throws IOException, ClassNotFoundException
    {
        ObjectStreamClass desc = new ObjectStreamClass();
        desc.readNonProxy(this);
        return desc;
    }
  这个方法从序列化的字节流中读取类描述信息,它在重建Java对象时创建了一个新的ObjectStreamClass对象,然后调用该对象的readNonProxy方法作为默认实现,在子类重写该方法时,它的默认实现是传入了this参数给readNonProxy方法;
  readTypeString
    String readTypeString() throws IOException {
        int oldHandle = passHandle;
        try {
            byte tc = bin.peekByte();
            switch (tc) {
                case TC_NULL:
                    return (String) readNull();

                case TC_REFERENCE:
                    return (String) readHandle(false);

                case TC_STRING:
                case TC_LONGSTRING:
                    return readString(false);

                default:
                    throw new StreamCorruptedException(
                        String.format("invalid type code: %02X", tc));
            }
        } finally {
            passHandle = oldHandle;
        }
    }
  该方法主要用于读取String对象,因为String在字节流中包含了三种读取方式:
  • TC_NULL
    如果读取了TC_NULL标记表示当前String对象是一个空引用
  • TC_REFERENCE
    如果读取了TC_REFERENCE标记表示读取的String对象是一个String的引用,这个对象之前已经在字节流中出现过;
  • TC_STRING 或 TC_LONGSTRING
    如果读取了TC_STRINGTC_LONGSTRING标记表示读取的是一个String的对象,调用readString方法来完成反序列化操作
  readClassDesc
    private ObjectStreamClass readClassDesc(boolean unshared)
        throws IOException
    {
        byte tc = bin.peekByte();
        switch (tc) {
            case TC_NULL:
                return (ObjectStreamClass) readNull();

            case TC_REFERENCE:
                return (ObjectStreamClass) readHandle(unshared);

            case TC_PROXYCLASSDESC:
                return readProxyDesc(unshared);

            case TC_CLASSDESC:
                return readNonProxyDesc(unshared);

            default:
                throw new StreamCorruptedException(
                    String.format("invalid type code: %02X", tc));
        }
    }
  该方法主要用于判断当前读取的对象的类型:
  • 如果读取的标记为TC_NULL则直接调用readNull方法;
  • 如果读取的标记为TC_REFERENCE则调用readHandle方法;
  • 如果读取的标记为TC_PROXYCLASSDESC则调用readProxyDesc方法返回动态代理类的类描述符;
  • 如果读取的标记为TC_CLASSDESC则调用readNonProxyDesc方法返回反序列化类的描述符;
  • 条件都不满足则抛出java.io.StreamCorruptedException异常;
  13)read核心方法
  readObject
    public final Object readObject()
        throws IOException, ClassNotFoundException
    {
        if (enableOverride) {
            return readObjectOverride();
        }

        // if nested read, passHandle contains handle of enclosing object
        int outerHandle = passHandle;
        try {
            Object obj = readObject0(false);
            handles.markDependency(outerHandle, passHandle);
            ClassNotFoundException ex = handles.lookupException(passHandle);
            if (ex != null) {
                throw ex;
            }
            if (depth == 0) {
                vlist.doCallbacks();
            }
            return obj;
        } finally {
            passHandle = outerHandle;
            if (closed && depth == 0) {
                clear();
            }
        }
    }
  该方法为ObjectInputStream对外的API,但是它并不是核心方法,它只是用于判断应该调用readObjectOverride还是readObject0方法,在反序列执行完成过后,它会调用vlist成员的doCallbacks来执行完成过后的回调逻辑
  readObjectOverride
    protected Object readObjectOverride()
        throws IOException, ClassNotFoundException
    {
        return null;
    }
  该方法的默认实现直接返回了null,这个方法存在的目的是为子类重写该方法。
  readObject0
    private Object readObject0(boolean unshared) throws IOException {
        boolean oldMode = bin.getBlockDataMode();
        if (oldMode) {
            int remain = bin.currentBlockRemaining();
            if (remain > 0) {
                throw new OptionalDataException(remain);
            } else if (defaultDataEnd) {
                /*
                 * Fix for 4360508: stream is currently at the end of a field
                 * value block written via default serialization; since there
                 * is no terminating TC_ENDBLOCKDATA tag, simulate
                 * end-of-custom-data behavior explicitly.
                 */
                throw new OptionalDataException(true);
            }
            bin.setBlockDataMode(false);
        }

        byte tc;
        while ((tc = bin.peekByte()) == TC_RESET) {
            bin.readByte();
            handleReset();
        }

        depth++;
        try {
            switch (tc) {
                case TC_NULL:
                    return readNull();

                case TC_REFERENCE:
                    return readHandle(unshared);

                case TC_CLASS:
                    return readClass(unshared);

                case TC_CLASSDESC:
                case TC_PROXYCLASSDESC:
                    return readClassDesc(unshared);

                case TC_STRING:
                case TC_LONGSTRING:
                    return checkResolve(readString(unshared));

                case TC_ARRAY:
                    return checkResolve(readArray(unshared));

                case TC_ENUM:
                    return checkResolve(readEnum(unshared));

                case TC_OBJECT:
                    return checkResolve(readOrdinaryObject(unshared));

                case TC_EXCEPTION:
                    IOException ex = readFatalException();
                    throw new WriteAbortedException("writing aborted", ex);

                case TC_BLOCKDATA:
                case TC_BLOCKDATALONG:
                    if (oldMode) {
                        bin.setBlockDataMode(true);
                        bin.peek();             // force header read
                        throw new OptionalDataException(
                            bin.currentBlockRemaining());
                    } else {
                        throw new StreamCorruptedException(
                            "unexpected block data");
                    }

                case TC_ENDBLOCKDATA:
                    if (oldMode) {
                        throw new OptionalDataException(true);
                    } else {
                        throw new StreamCorruptedException(
                            "unexpected end of block data");
                    }

                default:
                    throw new StreamCorruptedException(
                        String.format("invalid type code: %02X", tc));
            }
        } finally {
            depth--;
            bin.setBlockDataMode(oldMode);
        }
    }
  该方法在ObjectInputStream中才是readObject方法的核心方法,主要用于读取对象信息;它的详细过程如下:
  1. 先获取当前读取模式,检查是否是Data Block模式读取;
  2. 如果是Data Block模式,则先计算字节流中剩余字节数量,剩余数量大于0或者defaultDataEnd的值为true没有数据的情况则抛出java.io.OptionalDataException异常信息——因为这个方法主要负责读取对象类型的数据,这些数据虽然本身是一个Data Block,但是在字节流中它并没有使用TC_BLOCKDATA标记,以及TC_BLOCKDATALONG标记开始,所以这个地方一旦发现还存在这两种类型Data Block数据段则直接抛出异常;
    计算完成过后关闭Data Block模式
  3. 从字节流中看是否可读取到TC_RESET标记,如果读取到了该标记则调用handleReset方法;
  4. 根据读取的标记执行反序列化运算:
    如果读取TC_NULL——调用readNull函数;
    如果读取TC_REFERENCE——调用readHandle函数;
    如果读取TC_CLASS——调用readClass函数;
    如果读取TC_CLASSDESCTC_PROXYCLASSDESC——调用readClassDesc函数;
    如果读取TC_STRINGTC_LONGSTRING——调用readString函数;
    如果读取TC_ARRAY——调用readArray函数;
    如果读取TC_ENUM——调用readEnum函数;
    如果读取TC_OBJECT——调用readOrdinaryObject函数;
    如果读取TC_EXCEPTION——调用readFatalExcception函数;
    如果读取TC_BLOCKDATATC_BLOCKDATALONG——抛出异常信息,只是Data Block模式不同则抛出的异常信息不一样,开启Data Block模式
    如果读取TC_ENDBLOCKDATA——抛出异常信息,同上,只是不开启Data Block模式
    其他情况直接抛出异常信息;
  5. 需要注意的是在第四步中TC_ARRAY,TC_ENUM,TC_OBJECT,TC_STRING以及TC_LONGSTRING几种标记会对读取的返回结果调用checkResolve方法以检查反序列化的对象中是否重写了readResolve方法,如果重写了需要执行“Resolve”流程;
  6. 最后还原Data Block的原始模式,第一步读取的是什么值就还原成什么值;
  *:这里的readObject0方法的详细流程读者可参考JVM的序列化规范中的readObject方法的概念描述来理解,这样就可以知道readObject方法究竟做了写什么事;
  readStreamHeader
    protected void readStreamHeader()
        throws IOException, StreamCorruptedException
    {
        short s0 = bin.readShort();
        short s1 = bin.readShort();
        if (s0 != STREAM_MAGIC || s1 != STREAM_VERSION) {
            throw new StreamCorruptedException(
                String.format("invalid stream header: %04X%04X", s0, s1));
        }
    }
  这个方法很简单负责读取魔数和序列化版本信息;
  readUnshared
    public Object readUnshared() throws IOException, ClassNotFoundException {
        // if nested read, passHandle contains handle of enclosing object
        int outerHandle = passHandle;
        try {
            Object obj = readObject0(true);
            handles.markDependency(outerHandle, passHandle);
            ClassNotFoundException ex = handles.lookupException(passHandle);
            if (ex != null) {
                throw ex;
            }
            if (depth == 0) {
                vlist.doCallbacks();
            }
            return obj;
        } finally {
            passHandle = outerHandle;
            if (closed && depth == 0) {
                clear();
            }
        }
    }
  以“unshared”的方式从字节流中读取Java对象,直接调用readObject0方法重建Java对象,完成反序列化过后执行回调逻辑;
  14)子类实现方法
  resolveClass
    protected Class resolveClass(ObjectStreamClass desc)
        throws IOException, ClassNotFoundException
    {
        String name = desc.getName();
        try {
            return Class.forName(name, false, latestUserDefinedLoader());
        } catch (ClassNotFoundException ex) {
            Class cl = primClasses.get(name);
            if (cl != null) {
                return cl;
            } else {
                throw ex;
            }
        }
    }
  resolveProxyClass
    protected Class resolveProxyClass(String[] interfaces)
        throws IOException, ClassNotFoundException
    {
        ClassLoader latestLoader = latestUserDefinedLoader();
        ClassLoader nonPublicLoader = null;
        boolean hasNonPublicInterface = false;

        // define proxy in class loader of non-public interface(s), if any
        Class[] classObjs = new Class[interfaces.length];
        for (int i = 0; i < interfaces.length; i++) {
            Class cl = Class.forName(interfaces[i], false, latestLoader);
            if ((cl.getModifiers() & Modifier.PUBLIC) == 0) {
                if (hasNonPublicInterface) {
                    if (nonPublicLoader != cl.getClassLoader()) {
                        throw new IllegalAccessError(
                            "conflicting non-public interface class loaders");
                    }
                } else {
                    nonPublicLoader = cl.getClassLoader();
                    hasNonPublicInterface = true;
                }
            }
            classObjs[i] = cl;
        }
        try {
            return Proxy.getProxyClass(
                hasNonPublicInterface ? nonPublicLoader : latestLoader,
                classObjs);
        } catch (IllegalArgumentException e) {
            throw new ClassNotFoundException(null, e);
        }
    }
  resolveObject
    protected Object resolveObject(Object obj) throws IOException {
        return obj;
    }
  resolveClass、resolveProxyClassresolveObject方法和ObjectOutputStream中定义的annotateClass、annotateProxyClassreplaceObject是对应的,JVM的序列化规范中有说明,如果ObjectOutputStream的子类重写了它的三个方法,则ObjectInputStream的子类也需要重写对应的方法。ObjectInputStreamresolveClassresolveProxyClass中提供了默认实现——这是和序列化的方法唯一不同的地方;最后看看这三个方法的详细说明:
  • resolveClass
    该方法根据字节流中读取的类描述信息加载本地类,子类如果实现了这个方法则有可能读取源有所变化,默认实现是从字节流中读取,但重写了该方法可变换读取源;ObjectOutputStream中的annotateClass方法与之对应,和annotateClass方法一样该方法在字节流中针对每一个类的元数据信息只会调用唯一的一次,它虽然可以被子类重写和修改,但该方法的最终返回值必须是一个Class类型的对象;一旦返回过后,如果Class描述的对象不是一个array数组,则计算这个类中的serialVersionUID看是否匹配,不匹配则抛出java.lang.InvalidClassException异常;
    这个方法的默认实现中第三个参数是ClassLoader,这个ClassLoader会执行下边的步骤:
    a.如果栈顶发现的ClassLoader是用户自定义的一个ClassLoader,它会采取就近原则获取该方法帧数据中最近的ClassLoader;
    b.否则直接返回null表示当前ClassLoader是一个空引用
    在调用Class.forName时若出现了ClassNotFoundException异常信息直接抛出该异常信息;如果传入的基础类型是java关键字或void,则这个Class会表示一个基础类型的数据,使用封装类型代替,若是void则直接返回。(如果resolve时发现int类型,则直接返回Integer.TYPE类型,否则直接抛出异常信息)
  • resolveProxyClass
    该类主要处理动态代理接口,子类可重写用于解析动态代理数据信息,该类的默认实现这里不解析;
  • resolveObject
    该类允许ObjectInputStream可信任子类在反序列化过程中替换另外一个对象。默认情况下“Resolve”功能是关闭的,一旦调用了enableResolveObject方法就会启用“Resolve”功能,enableResolveObject方法在调用到时候会检查字节流中用于Resolve的对象是否是可信任的。每一个可序列化对象的引用都会传入resolveObject。为了确保对象私有状态不会暴露,只有可信任的字节流可使用resolveObject方法;
    a.这个方法会在一个Java对象从字节流中读取过后,在该方法调用readObject方法之前调用,默认resolveObject方法仅仅是返回了同样的对象
    b.如果子类正在替换对象,则它必须保证替代对象是和引用指向的对象兼容的。如果对象类型并不是字段的子类或者数组类的元素则中断反序列化过程抛出异常,这种情况下恢复Java对象失败;
    c.这个方法仅仅在第一次读取同类型的Java对象时调用,之后读取到的该对象的引用则直接指向原始对象
  enableResolveObject
    protected boolean enableResolveObject(boolean enable)
        throws SecurityException
    {
        if (enable == enableResolve) {
            return enable;
        }
        if (enable) {
            SecurityManager sm = System.getSecurityManager();
            if (sm != null) {
                sm.checkPermission(SUBSTITUTION_PERMISSION);
            }
        }
        enableResolve = enable;
        return !enableResolve;
    }
  该方法会启用反序列化过程中的“Resolve”功能
  • 如果该功能启用,并且传入参数为true,则什么都不做直接返回;
  • 如果想要启用“Resolve”功能,则需要调用Java的安全管理器并检查代码执行权限SUBSTITUTION_PERMISSION
  15)构造方法
  该类的构造方法如下:
    public ObjectInputStream(InputStream in) throws IOException {
        verifySubclass();
        bin = new BlockDataInputStream(in);
        handles = new HandleTable(10);
        vlist = new ValidationList();
        enableOverride = false;
        readStreamHeader();
        bin.setBlockDataMode(true);
    }

    protected ObjectInputStream() throws IOException, SecurityException {
        SecurityManager sm = System.getSecurityManager();
        if (sm != null) {
            sm.checkPermission(SUBCLASS_IMPLEMENTATION_PERMISSION);
        }
        bin = null;
        handles = null;
        vlist = null;
        enableOverride = true;
    }
  ObjectInputStreamObjectOutputStream类一样有两个构造方法,一个public单参数构造函数,一个protected无参构造函数,看看两个构造函数的详细流程:
  protected构造函数
  1. 调用安全管理器SecurityManager,并且使用安全管理器检查权限SUBCLASS_IMPLEMENTATION_PERMISSION
  2. 设置成员属性的默认值:
    bin——nullhandles——nullvlist——nullenableOverride——true
  public构造函数:
  1. 调用verifySubclass方法验证子类信息;
  2. 初始化bin成员,实例化一个BlockDataInputStream
  3. 初始化handlesvlist,创建两个对应的实例;
  4. enableOverride的值设成false
  5. 调用readStreamHeader方法读取魔数和序列化版本信息;
  6. 开启Data Block模式读取信息;
  到这里ObjectInputStream中的大部分方法都已经解析完成了,其他的辅助方法和相关成员函数这里就不解析了,下一个章节将提供一些例子来解析Java序列化中的面向对象的特性;第一章解析了Java序列化生成二进制文件的文件结构,从文件结构的角度剖析了Java序列化的相关原理和算法;后边的章节将根据源代码中的代码执行流程来分析Java序列化如何来生成对应的二进制文件,从另外的一个角度再来回顾Java序列化的相关原理和算法。

5.序列化原理和算法——面向对象
  本章会提供大量的实例来解析序列化中的一些面向对象的特性,尽可能覆盖一些常见的序列化问题包括一些比较争议的概念,本章节的解析主要不会针对生成的二进制文件的结构,仅仅根据Java语言特性来分析二进制文件生成过程以及讨论一些和面向对象相关的问题。
  i.数组的序列化分析
  先看个例子:
package org.susan.java.serial;

import java.io.FileOutputStream;
import java.io.ObjectOutputStream;
import java.io.Serializable;

public class ArraySerial implements Serializable{
	/**
	 * 
	 */
	private static final long serialVersionUID = 749500769727730567L;
	private String name;
	public ArraySerial(String name,int age){
		this.name = name;
		this.age = age;
	}
	public String getName() {
		return name;
	}
	public void setName(String name) {
		this.name = name;
	}
	public int getAge() {
		return age;
	}
	public void setAge(int age) {
		this.age = age;
	}
	private int age;
	// 运行函数
	public static void main(String args[]) throws Exception{
		ObjectOutputStream out = new ObjectOutputStream(new FileOutputStream("array.obj"));
		// 序列化普通数组
		short[] arr = new short[4];
		for( int i = 0; i < 4; i++ ){
			short ele = (short) (Math.random() * 100);
			arr[i] = ele;
		}
		out.writeObject(arr);
		// 序列化对象数组
		ArraySerial[] objArr = new ArraySerial[4];
		for( int i = 0; i < 4; i++ ){
			objArr[i] = new ArraySerial("LangYu"+i,(int) (Math.random() * 60));
		}
		out.writeObject(objArr);
		out.flush();
		out.close();
	}
}
  上述示例中创建了两个数组:一个基础类型的数组,一个对象类型的数组,随后调用writeObject方法将这些数据写入到目标介质文件中,分析这个示例之前,我们先根据代码的流程一步一步解析它会生成什么样的二进制文件。注意括号内的内容,括号内注明了截取的代码的具体位置,带【】符号的步骤表示会生成二进制序列的步骤!
  1. 第一步实例化一个ObjectOutputStream,在实例化时调用了它的单参数构造函数,在调用该构造函数时会优先调用verifySubclass方法。该方法执行时会在第一个return处直接返回,因为该方法中调用getClass方法时返回的Class类型就为ObjectOutputStream
    ObjectOutputStream--> verifySubclass

    	Class cl = getClass();
    	if (cl == ObjectOutputStream.class) {
    	    return;	
    	}
  2. verifySubclass验证完过后会初始化成员属性:bouthandlessubsenableOverride
    ObjectOutputStream --> 

    	bout = new BlockDataOutputStream(out);
    	handles = new HandleTable(10, (float) 3.00);
    	subs = new ReplaceTable(10, (float) 3.00);
    	enableOverride = false;
  3. AC ED 00 05随后会调用writeStreamHeader方法,这个方法会往缓冲区中写入两个特殊标记STREAM_MAGICSTREAM_VERSION
    ObjectOutputStream --> writeStreamHeader

    	bout.writeShort(STREAM_MAGIC);
    	bout.writeShort(STREAM_VERSION);
  4. 开启输出流的Data Block模式
    ObjectOutputStream  --> 

    	bout.setBlockDataMode(true);
  5. 因为extendedDebugInfo成员属性为false,所以并未开启Debug模式;
  6. 调用ObjectOutputStream的公共API成员函数writeObjectwriteObject会先判断enableOverride成员属性的值,该值在第二步已经设置成false了;
    ObjectOutputStream --> writeObject

    	if (enableOverride) {
    	    writeObjectOverride(obj);
    	    return;
    	}
  7. 因为enableOverride成员属性值为false,所以直接调用writeObject0成员函数,注意传入这个函数的第二个参数;在代码中第二个参数传入了false,证明使用的Java对象序列化方式不是“unshared”的方式;
    ObjectOutputStream --> writeObject

    	    writeObject0(obj, false);
  8. 因为写入的是新对象,所以它不满足代码写入第一块的内容:1)写入引用并非null引用;2)写入的对象在原始字节流中并未出现过;3)写入的对象不是特殊对象类型ClassObjectStreamClass
    ObjectOutputStream --> writeObject0

    		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;
    		}
  9. 因为写入的是array对象,它的内部并没有重写writeReplace方法,所以直接跳过writeObject0方法中的enableReplace检测,同样不执行针对“替换对象”的字节写入流程;
  10. 除了上述的类型判断以外,接下来进入针对Array对象的写入过程,进入下边代码的第二个分支:
    ObjectOutputStream --> writeObject0

    		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());
    			}
    		}
  11. 75调用writeArray方法写入array对象二进制序列段数据,writeArray有三个形参:
    ObjectOutputStream --> writeArray
    array——java.lang.Object:传入的对象本身的信息;
    desc——java.io.ObjectStreamClass:传入的对象所属类的元数据信息
    unshared——boolean:传入对象序列化的方式,从外层writeObject方法中可以知道这个值是false的;
    先调用bout成员函数的writeBytes方法写入TC_ARRAY标记,随后调用writeClassDesc方法写入array对象所属类元数据信息

    bout.writeByte(TC_ARRAY);
    writeClassDesc(desc, false);
    
  12. writeClassDesc方法有四个分支,这个四个分支代码这里不枚举,实际上在写入array对象所属类的元数据时,会调用最后一个分支的writeNonProxyDesc方法,只是需要注意的是在调用writeClassDesc方法时第二个参数同样传入的是一个固定的false,并没将上层调用中的“unshared”方式的值传入这里;
  13. 72writeNonProxyDesc方法会先写入TC_CLASSDESC标记,随后将该类元数据的引用Handle插入到handles成员属性中:
    ObjectOutputStream --> writeNonProxyDesc

    	bout.writeByte(TC_CLASSDESC);
    	handles.assign(unshared ? null : desc);
  14. 判断序列化流协议的版本,不同的版本需要调用不同的方法,如果是PROTOCOL_VERSION_1协议,调用writeNonProxy方法,如果不是PROTOCOL_VERSION_1协议,则调用writeClassDescriptor方法;
    ObjectOutputStream --> writeNonProxyDesc

    	if (protocol == PROTOCOL_VERSION_1) {
    	    // do not invoke class descriptor write hook with old protocol
    	    desc.writeNonProxy(this);
    	} else {
    	    writeClassDescriptor(desc);
    	}
  15. 因为示例中使用的是JDK 1.5以上的JDK环境,所以使用的是PROTOCOL_VERSION_1协议,直接调用writeClassDescriptor方法,调用时使用该方法的默认实现;
    ObjectOutputStream --> writeClassDescriptor

    	desc.writeNonProxy(this);
  16. 00 02 5B 53 EF 83 2E 06 E5 5D B0 FA写入array数组的名称和serialVersionUID的值,需要注意的是数组的类的serialVersionUID的值是调用computeDefaultSUID方法计算的,因为类型是short[],所以类名应该是“[S5B 53,SUID的计算细节就不在这里重复了,读者可以根据SUID的算法自己去实践实践,计算出来的值是8个字节的long类型数据,为EF 83 2E 06 E5 5D B0 FA;可以读者一定有个疑问,这一段序列的前缀【00 02】是怎么产生的?不知读者是否记得在写入TC_CLASSDESC过后,会先写入类型名称长度,再写入类型名称内容——writeUTF函数会处理这个细节,00 02就是short整数类型的值(“[S”.length() ==2)。注意:这里写入的“[S”不是签名信息,而是类名信息,请读者区分类名签名的概念
    ObjectStreamClass --> writeNonProxy

    	out.writeUTF(name);
    	out.writeLong(getSerialVersionUID());
  17. 为了不让读者困惑,这个地方再看看writeUTF成员函数的定义,这样就彻底理解00 02为什么会出现在这个地方了:
    ObjectOutputStream.BlockDataOutputStream--> writeUTF

    	void writeUTF(String s, long utflen) throws IOException {
    		if (utflen > 0xFFFFL) {
    			throw new UTFDataFormatException();
    		}
    		writeShort((int) utflen);
    		if (utflen == (long) s.length()) {
    			writeBytes(s);
    		} else {
    			writeUTFBody(s);
    		}
    	}
  18. 判断当前数组对应的SC_*标记,因为Java中的数组都是默认实现Serializable接口,所以flag标记的值为ObjectStreamConstants.SC_SERIALIZABLE
    ObjectStreamClass--> writeNonProxy

    	    flags |= ObjectStreamConstants.SC_SERIALIZABLE;
  19. 02写入SC_SERIALIZABLE标记值,前文解析了过了02的含义,但是里面只是说明了02表示对象所属类是“实现了Serializable接口可支持序列化的”,那么从这里可以知道为什么02的值了;
    ObjectStreamClass --> writeNonProxy

    	out.writeByte(flags);
  20. 00 00写入short[]类型中成员属性数量信息,因为short[]的成员属性的数量为0,所以这里写入的是一个short类型的值,为00 00
    ObjectStreamClass --> writeNonProxy

    	out.writeShort(fields.length);
  21. 因为当前这个数组中没有任何成员函数信息,所以针对每个成员函数就到此为止了,写入了当前类的元数据信息过后,会在Data Block模式下调用annotateClass方法;
    ObjectOutputStream --> writeNonProxyDesc

    	Class cl = desc.forClass();
    	bout.setBlockDataMode(true);
    	annotateClass(cl);
    	bout.setBlockDataMode(false);
  22. 78因为当前的类描述信息已经写入完毕,所以在最后写入TC_ENDBLOCKDATA标记;
    ObjectOutputStream --> writeNonProxyDesc

    	bout.writeByte(TC_ENDBLOCKDATA);
  23. TC_ENDBLOCKDATA标记写入过后,再调用writeClassDesc写入当前Java对象的父类信息,注意2点:
    1)writeNonProxyDesc调用了writeClassDesc方法,而writeClassDesc的判断条件中也会调用writeNonProxyDesc方法,这个地方是一个循环调用,使得系统可实现“从下至上递归遍历”
    2)当一个类的直接父类Object时,getSuperDesc成员函数调用会返回null
    ObjectOutputStream --> writeNonProxyDesc

    	writeClassDesc(desc.getSuperDesc(), false);
  24. 70writeClassDesc中调用writeNull成员函数,这个函数会写入TC_NULL标记;
    ObjectOutputStream --> writeNull

    	bout.writeByte(TC_NULL);
  25. 接下来的代码段会写入该数组中的数据,先看看代码中这个数组的信息,类型为short[],长度为4,它的元素类型为基本类型(通过getComponentType获取元素的数据类型);
    ObjectOutputStream--> writeArray

    	Class ccl = desc.forClass().getComponentType();
    	if (ccl.isPrimitive()) {
                    // 如果元素是基础类型的代码段
                    ...
    	} else {
                    // 如果元素是对象类型的代码段
                    ...
    	}
  26. 00 00 00 04 00 26 00 5D 00 58 00 31因为数组的元素是short类型,所以ccl.isPrimitive方法会返回true,随后调用bout成员函数的writeInt方法写入数组长度,在调用writeShorts方法直接将该数组中的每一个元素写入字节流;
    注:这段序列的最后8个字节表示4short值,因为每一个值调用了(short) (Math.random() * 100)方法获得,所以这8个字节表示4随机数——所以这8个字节每一次运行时结果都不一样
    ObjectOutputStream --> writeArray

    		short[] sa = (short[]) array;
    		bout.writeInt(sa.length);
    		bout.writeShorts(sa, 0, sa.length);
  27. 在代码最后会还原Data Block模式,并且修改depth成员属性的值;
    ObjectOutputStream --> writeObject0

    	} finally {
    	    depth--;
    	    bout.setBlockDataMode(oldMode);
    	}
  28. 到这里主代码中out.writeObject(arr)就执行完毕了,接下来看看对象数组的代码流程,结合上边的6~27步看看两种数组的二进制序列生成的差异,前边相同的部分这里不再解析;
    75 72写入的TC_ARRAYTC_CLASSDESC两个标记;
    00 24写入数组的名称长度,名称的内容为:“[Lorg.susan.java.serial.ArraySerial;”,这个字符串长度为36,换算成十六进制24
    5B 4C 6F 72 67 2E 73 75 73 61 6E 2E 6A 61 76 61 2E 73 65 72 69 61 6C 2E 41 72 72 61 79 53 65 72 69 61 6C 3B字符串“[Lorg.susan.java.serial.ArraySerial;”
    98 52 3B E3 AD 37 EE 9D当前这个数组的serialVersionUID的值;
    02写入的标记SC_SERIALIZABLE的值;
    00 00表示当前这个对象数组中没有任何成员属性,所以成员属性的数量0
    78 70表示标记TC_ENDBLOCKDATATC_NULL
    到这里对象数组类描述信息【元数据信息就执行完成了,和上边的步骤11~24的代码是一模一样的;
  29. 00 00 00 04对象数组的复杂在于针对每一个元素的写入过程,也就是从上边第25步开始有变化,调用ccl.isPrimitive方法会返回false,走入另外一个代码流程,先写入对象数组的长度信息,示例中数组长度为4
    ObjectOutputStream --> writeArray

    	    Object[] objs = (Object[]) array;
    	    int len = objs.length;
    	    bout.writeInt(len);
  30. 写入对象数组的长度信息过后,接着写入每一个元素的信息,因为元素的类型是对象类型,所以调用writeObject0方法,注意这部分信息的调用可开启Debug调试模式,代码中会针对extendedDebugInfo成员属性进行检测;
    ObjectOutputStream --> writeArray

    		for (int i = 0; i < len; i++) {
    			if (extendedDebugInfo) {
    				debugInfoStack.push("element of array (index: " + i + ")");
    			}
    			try {
    				writeObject0(objs[i], false);
    			} finally {
    				if (extendedDebugInfo) {
    					debugInfoStack.pop();
    				}
    			}
    		}
  31. writeObject0方法的主要流程这里就不阐述了,这里调用的是writeOrdinaryObject方法;
    ObjectOutputStream --> writeObject0

    		writeOrdinaryObject(obj, desc, unshared);
  32. 在调用该方法时,系统会先调用ObjectStreamClass类的checkSerialize方法检查数组中的元素是否可支持序列化,这里检查的类为org.susan.java.serial.ArraySerial,如果该类不支持序列化,checkSerialize方法会抛出异常信息
    ObjectOutputStream --> writeOrdinaryObject

    	    desc.checkSerialize();
  33. 73在写入数组元素时候先写入TC_OBJECT标记信息,跟随其后调用writeClassDesc方法写入该对象所属类元数据信息,该方法内部又调用了writeNonProxyDesc方法;
    ObjectOutputStream --> writeOrdinaryObject

    	    bout.writeByte(TC_OBJECT);
    	    writeClassDesc(desc, false);
  34. 72调用writeNonProxyDesc方法写入TC_CLASSDESC标记,随后调用writeClassDescriptor方法写入对应的类描述信息
    ObjectOutputStream --> writeNonProxyDesc

    	bout.writeByte(TC_CLASSDESC);
    	handles.assign(unshared ? null : desc);
  35. 00 21 6F 72 67 2E 73 75 73 61 6E 2E 6A 61 76 61 2E 73 65 72 69 61 6C 2E 41 72 72 61 79 53 65 72 69 61 6C在写入类描述信息时会先写入类名,类名写入调用了writeUTF方法,其格式为“长度 名称”,这段代码生成的二进制序列如下:
    00 21段:类名长度为33十六进制值为21,之后所有生成的二进制的值为字符串:“org.susan.java.serial.ArraySerial”
    ObjectStreamClass --> writeNonProxy

    	out.writeUTF(name);
    	out.writeLong(getSerialVersionUID());
  36. 0A 66 C2 FA A2 80 D3 87随后写入SUID的信息(见第35步),该serialVersionUID和上边写入SUID的值不一样,这个值是直接提取的源代码中定义的值749500769727730567
  37. 02随后写入SC_SERIALIZABLE标记信息;
  38. 从这一步开始,和上边写入数组的描述信息就有所不同了,仔细看看这段写入字段信息的代码,这段代码会先写入该类中的成员属性的数量:
    00 02该类中包含了两个字段nameage,所以这里写入的二进制数据是00 02
    49 00 03 61 67 6549类型代码,值为“I”00 03表示属性age的名称长度,61 67 65表示属性名称字符串“age”
    4C 00 04 6E 61 6D 654C为类型代码,值为“L”00 04表示属性name的名称长度,6E 61 6D 65表示属性名称字符串“name”
    注意该类中存在一个判断,在判断内部会调用writeTypeString方法,该方法会根据传入的String类型分别调用三个不同函数:
    null——调用writeNull
    引用Handle——调用writeHandle
    String对象——调用writeString
    74 00 12 4C 6A 61 76 61 2F 6C 61 6E 67 2F 53 74 72 69 6E 67 3B因为这里是第一次遇到String类型的对象,所以这里会先写入String的元数据信息74表示TC_STRING标记,00 12表示类型名的长度,最后18个字节表示类型字符串“Ljava/lang/String;”
    ObjectStreamClass --> writeNonProxy

    		out.writeShort(fields.length);
    		for (int i = 0; i < fields.length; i++) {
    			ObjectStreamField f = fields[i];
    			out.writeByte(f.getTypeCode());
    			out.writeUTF(f.getName());
    			if (!f.isPrimitive()) {
    				out.writeTypeString(f.getTypeString());
    			}
    		}
  39. 78 70最后写入TC_ENDBLOCKDATA标记和TC_NULL标记,表示String类的元数据结束
  40. 在完成元数据的写入信息过后,需要判断序列化的类是“默认序列化”还是“外部化”,如果是“外部化”调用writeExternalData方法,如果是“默认序列化”调用writeSerialData方法;
    ObjectOutputStream --> writeOrdinaryObject

    		handles.assign(unshared ? null : obj);
    		if (desc.isExternalizable() && !desc.isProxy()) {
    			writeExternalData((Externalizable) obj);
    		} else {
    			writeSerialData(obj, desc);
    		}
  41. 在调用writeSerialData方法时会先调用默认序列化字段数据的方法defaultWriteFields
    ObjectOutputStream --> writeOrdinaryObject

    		defaultWriteFields(obj, slotDesc);
  42. 在调用defaultWriteFields方法之前,会先调用ObjectStreamClass类的checkDefaultSerialize方法检查该对象所属类是否支持默认序列化机制;
    ObjectOutputStream --> defaultWriteFields

    	desc.checkDefaultSerialize();
  43. 00 00 00 2F先写入age属性的值,这个值也是60之内的一个随机数,每次运行的时候不一样
    ObjectOutputStream --> writeOrdinaryObject

    	int primDataSize = desc.getPrimDataSize();
    	if (primVals == null || primVals.length < primDataSize) {
    	    primVals = new byte[primDataSize];
    	}
    	desc.getPrimFieldValues(obj, primVals);
    	bout.write(primVals, 0, primDataSize, false);
  44. 74 00 07 4C 61 6E 67 59 75 30随后调用writeObject0方法写入name属性的值,这样对象数组第一个元素就完成了写入操作;
  45. 73 71 00 7E 00 04这段序列是数组元素中第二个元素的元数据信息,和第一个元素不一样,第一个元素写入的是TC_OBJECT标记,第二个元素写入的就是TC_REFERENCE标记了;
    ObjectOutputStream --> writeHandle

    	bout.writeByte(TC_REFERENCE);
    	bout.writeInt(baseWireHandle + handle);
  46. 接下来就直接看看最后的几段序列的生成:
    00 00 00 26 74 00 07 4C 61 6E 67 59 75 31第二个元素agename的值;
    73 71 00 7E 00 04第三个元素的引用Handle
    00 00 00 1C 74 00 07 4C 61 6E 67 59 75 32第三个元素agename的值;
    73 71 00 7E 00 04第四个元素的引用Handle
    00 00 00 26 74 00 07 4C 61 6E 67 59 75 33第四个元素agename的值;
  47. 输出了上述的序列过后整个二进制序列的生成过程就结束了,而之后的代码这儿就不详细解析了;
  上边的流程已经非常详细地解析了代码中生成二进制序列的步骤,最后看看整个二进制序列:
AC ED 00 05 75 72 00 02 5B 53 EF 83 2E 06 E5 5D 
B0 FA
 02 00 00 78 70 00 00 00 04 00 26 00 5D 00 
58 00 31
 75 72 00 24 5B 4C 6F 72 67 2E 73 75 73 
61 6E 2E 6A 61 76 61 2E 73 65 72 69 61 6C 2E 41 
72 72 61 79 53 65 72 69 61 6C 3B
 98 52 3B E3 AD 
37 EE 9D
 02 00 00 78 70 00 00 00 04 73 72 00 21 
6F 72 67 2E 73 75 73 61 6E 2E 6A 61 76 61 2E 73 
65 72 69 61 6C 2E 41 72 72 61 79 53 65 72 69 61 
6C
 0A 66 C2 FA A2 80 D3 87 02 00 02 49 00 03 61 
67 65 
4C 00 04 6E 61 6D 65 74 00 12 4C 6A 61 76 
61 2F 6C 61 6E 67 2F 53 74 72 69 6E 67 3B
 78 70 
00 00 00 2F 74 00 07 4C 61 6E 67 59 75 30 73 71 
00 7E 00 04 00 00 00 26 74 00 07 4C 61 6E 67 59 
75 31
 73 71 00 7E 00 04 00 00 00 1C 74 00 07 4C 
61 6E 67 59 75 32
 73 71 00 7E 00 04 00 00 00 26 
74 00 07 4C 61 6E 67 59 75 33

  上边的二进制序列是不是和我们步骤中的解析一模一样呢?简单总结一下这个解析的几个注意点:
  • 这段序列中存在随机数,所以读者在运行过后生成的序列可能会和上边列出来的序列有所差异;
  • 注意代码中的调试段,调试段的内容只有在序列化的时候存在,反序列化时没有提供调试部分的代码;
  • 这里的代码流程解析只是解析了序列化的过程,并没有解析反序列化的数据读取过程;

  ii.非序列化超类的序列化
  这个章节开始就不会再去分析二进制文件的内容了,只会在必要的时候插入几段二进制序列代码来辅助分析过程;
  如果一个可序列化的子类的直接子类既不是Object,而它的父类不可序列化,那么会出现什么情况,先看看例子:
package org.susan.java.serial;

import java.io.FileInputStream;
import java.io.FileOutputStream;
// import java.io.IOException;
import java.io.ObjectInputStream;
import java.io.ObjectOutputStream;
import java.io.Serializable;

public class CaseOne {
	// 执行函数
	public static void main(String args[]) throws Exception{
		// 序列化
		ObjectOutputStream out = new ObjectOutputStream(new FileOutputStream("caseone.obj"));
		SubClass sub = new SubClass(true,"silentbalanceyh","Yu Lang");
		out.writeObject(sub);
		out.flush();
		out.close();
		// 反序列化
		ObjectInputStream in = new ObjectInputStream(new FileInputStream("caseone.obj"));
		SubClass subNew = (SubClass)in.readObject();
		in.close();
		System.out.println("SUB:" + subNew);
		System.out.println("SUPER:" + subNew.getSuper());
	}
}

class SubClass extends BaseClass implements Serializable{

	private static final long serialVersionUID = 7442418880476300463L;
	
	private boolean gender;
	private String name;
	private String author;
	
	public SubClass(){		
		super();
	}
	
	public SubClass(boolean gender,String name, String author){
		super(name,27);
		this.gender = gender;
		this.name = name;
		this.author = author;
	}
	
	public String getSuper(){
		return super.toString();
	}
	
/*	private void writeObject(ObjectOutputStream out) throws IOException{
		out.defaultWriteObject();
		out.writeObject(super.name);
		out.writeInt(age);
	}
	
	private void readObject(ObjectInputStream in) throws IOException,ClassNotFoundException{
		in.defaultReadObject();
		
		super.name = (String)in.readObject();
		age = (int)in.readInt();
	}*/

	@Override
	public String toString() {
		return "SubClass [gender=" + gender + ", name=" + name + ", author="
				+ author + "]";
	}
}

class BaseClass{
	@Override
	public String toString() {
		return "BaseClass [name=" + name + ", age=" + age + "]";
	}
	protected String name;
	protected int age;
	public BaseClass(){
	}
	public BaseClass(String name, int age){
		this.name = name;
		this.age = age;
	}
}
  看看上边代码的输出:
SUB:SubClass [gender=true, name=silentbalanceyh, author=Yu Lang]
SUPER:BaseClass [name=null, age=0]

  从上边的输出可以知道,如果一个可序列化的类的父类是不可序列化的,则在默认序列化过程父类会直接被忽略掉,如果需要序列化该父类的数据,只有在该类中重写writeObject方法和readObject方法来实现。但是有一点注意:如果父类和子类的成员属性同名,在writeObject以及readObject两个方法中,针对成员函数的调用最后显示使用thissuper来进行。去掉上边例子中的注释过后其输出为:
SUB:SubClass [gender=true, name=silentbalanceyh, author=Yu Lang]
SUPER:BaseClass [name=silentbalanceyh, age=27]

  如果上边的代码不使用super关键字,则BaseClassname属性就会为null值;

  iii.关于构造函数的调用
  前文已经讲解了序列化的大部分的内容,这里再看个例子:
package org.susan.java.serial;

import java.io.FileInputStream;
import java.io.FileOutputStream;
import java.io.ObjectInputStream;
import java.io.ObjectOutputStream;
import java.io.Serializable;

public class CaseTwo implements Serializable{
	private static final long serialVersionUID = -5719199880473656625L;
	public CaseTwo(){
		System.out.println("Called Constructor!");
	}
	public static void main(String args[]) throws Exception{
		// 序列化
		ObjectOutputStream out = new ObjectOutputStream(new FileOutputStream("casetwo.obj"));
		CaseTwo sub = new CaseTwo();
		out.writeObject(sub);
		out.flush();
		out.close();
		// 反序列化
		ObjectInputStream in = new ObjectInputStream(new FileInputStream("casetwo.obj"));
		@SuppressWarnings("unused")
		CaseTwo subNew = (CaseTwo)in.readObject();
		in.close();
	}
}
  这个例子的输出为:
Called Constructor
  需要说明的内容很简单:序列化机制在反序列对象的过程中并未调用构造函数,而是直接恢复的Java的状态数据信息;

  iv.抽象类&接口
  前文中一直没有提到抽象类和接口的字节结构,这里再看一个和它相关的例子:
package org.susan.java.serial;

import java.io.FileOutputStream;
import java.io.ObjectOutputStream;
import java.io.Serializable;

public class CaseThree {
	public static void main(String args[]) throws Exception {
		// 序列化
		ObjectOutputStream out = new ObjectOutputStream(new FileOutputStream(
				"casethree.obj"));
		BaseClass1 base = new BaseClass1();
		out.writeObject(base);
		out.flush();
		out.close();
	}
}

class BaseClass1 extends AClass1 implements AInterface,Serializable {

	private static final long serialVersionUID = 3530141157381778908L;

}

abstract class AClass1 implements CInterface {

}

interface AInterface extends BInterface {
	int name = 0;
}

interface BInterface {
	String bName = "Lang Yu";
}

interface CInterface {

}
  上述例子的对象结构如下:

  分析一下这段代码输出的二进制序列文件的详细内容:
  • AC ED 00 05魔数和序列化版本信息;
  • 73 72写入TC_OBJECTTC_CLASSDESC标记;
  • 00 20 6F 72 67 2E 73 75 73 61 6E 2E 6A 61 76 61 2E 73 65 72 69 61 6C 2E 42 61 73 65 43 6C 61 73 73 31类名的长度以及类名字符串:“org.susan.java.serial.BaseClass1”
  • 30 FD 94 AD DC BA 2D DC对应类的SUID的值;
  • 00 00表示这个类没有任何属性,属性数量为0;
  • 78 70表示TC_ENDBLOCKDATATC_NULL标记信息;
  读者一定有困惑,为什么这么复杂的结构二进制序列和普通的Object写入没有区别呢?确实如此,这里使用的新建对象的方法是BaseClass1 base = new BaseClass1();,即使这行代码像下边这种其二进制序列输出也一样。
AInterface base = new BaseClass1();
CInterface base = new BaseClass1();
BInterface base = new BaseClass1();
AClass1 base = new BaseClass1();
  上边的初始化方式生成的二进制序列是一模一样的,那么接口中的name,bName哪儿去了?——相信读者还记得接口常量【《Java中的类和对象》】,这里就不详讲了。

6.本章小结
  到这里基本涉及到Java序列化的整体知识就已经讲完了,其实Java源码分析占用了两个章节的内容,提供这么多详细的解析是希望读者从不同的角度去彻底理解Java序列化部分的详细内容,如果有疑问可以发送邮件到:[email protected],但是这里我不承诺能够全部回复。当然这个Blog我会尽可能不让它荒废掉,每隔一段时间我尽量发布一些比较高质量的文章在这里,希望读者理解一点的就是:我的Blog中的文章一般篇幅比较长,一篇文章可能在3~5万字之间,而每一部分Java知识点的话可能在8到10W字左右,所以没有办法“高产”,望谅解。


你可能感兴趣的:(Java系列教程)