本文共 56749 字,大约阅读时间需要 189 分钟。
(从09年回到重庆过后,就一直在工作,时间长了惰性就慢慢起来了,公司的项目从09年忙到了现在,一直没有时间来梳理自己的东西,CSDN的Blog似乎都荒废了,不知道现在还能否坚持把Blog完成,希望有一个新的开始吧!如果读者有问题还是可直接发邮件到silentbalanceyh@126.com,我也仅仅只是想把看的、写的、学的东西总结起来,让自己有个比较完整的学习记录。本文主要针对Java的序列化相关知识,先不涉及XML序列化和Json序列化的内容,这部分内容以后再议。着色的目的是强调重点和关键的概念以及防止读者通篇阅读的视觉疲劳,也是个人的写作风格,不习惯的读者请见谅!——本来打算把全文的内容放在一篇Blog中来讲解,慢慢地就发现Java序列化的内容其实蛮多的,篇幅长了过后排版慢慢变得比较复杂,写这么多的目的就是为了把我自己理解过、分析过、验证过以及开发的时候用过的内容分享给大家,也许我不能保证在CSDN上频繁地发BLOG,但每发一次尽可能保证这些内容的营养价值。)
本章目录:
1.Java中的序列化
2.序列化原理和算法——基础数据
3.深入序列化规范
4.源码分析
----ObjectStreamField
----ObjectStreamClass
----ObjectOutputStream
----ObjectInputStream
5.序列化原理和算法——面向对象
4.源码分析
在讲解本章的内容之前,先简单回顾一下:前文讲了Java中的序列化的基本概念,并且通过输出的二进制文件内容的分析理解了Java序列化的原理和算法,同样引领读者解析了JVM的序列化规范;第二章中我们分析了ObjectStreamField和ObjectStreamClass两个类的源代码。 其实迄今为止,所有的讲解和分析都是从一个角度在阐述——数据结构。二进制文件内容的分析实际上是让读者去理解Java序列化生成的目标数据是什么结构,而ObjectStreamField和ObjectStreamClass两个类主要用途就是用于描述对象所属类的元数据以及它所定义的成员属性的相关元数据,它们的源码中除了ObjectStreamClass中的readNonProxy和writeNonProxy包含了和序列化和反序列化执行过程的内容以外,其他所有的内容都是和数据结构相关的东西。 前边的章节我们一直讨论了下边几个问题:
- Java的序列化是什么?
- Java中内建序列化生成的二进制数据是什么结构?
- 基础类型的数据在不同的情况会如何写入到二进制文件中?
- Java的序列化规范中有些什么关键性概念?
- Java中序列化部分的源代码定义了一些什么数据结构模型?【ObjectStreamClass&ObjectStreamField】?
如果读者对上边的几个问题不理解,则回顾前文的序列化讲解,从本章开始,我们将解析ObjectOutputStream以及ObjectInputStream的源代码来理解Java序列化和反序列化,通过对代码的解读彻底理解它的内部原理和算法,第4章源码分析的目标如下【前文提过,这里仅做回顾】: - 详细分析Java序列化中使用的几个核心类的源代码,通过其内部执行流程换一种角度来看看Java序列化的知识;
- 针对JVM的序列化规范中的一些疑点提供示例来加以证明,进行规范的深入理解;
- 提供Java序列化中和面向对象、最佳实践的相关内容;
- 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的子类; 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 {
-
- static final ConcurrentMap<WeakClassKey,Boolean> subclassAudits =
- new ConcurrentHashMap<>();
-
-
- static final ReferenceQueue<Class<?>> subclassAuditsQueue =
- new ReferenceQueue<>();
- }
Caches类中包含了两个成员subclassAudits和subclasseAuditsQueue: 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抽象类中主要有9个put方法,以及一个已经过期的write方法,put方法主要将对应类型的字段值写入到持久化字段中,9个方法分别对应Java语言中的8个基础类型以及1个Object类型的字段的写入。它的参数如下:
- name——java.lang.String 一个类中定义的可序列化的字段名称;
- val 需要赋值给该字段的值,它的类型不一样则调用的方法就会不同,该类中定义的put方法根据参数类型不同而实现重载;
PutFieldImpl类是ObjectOutputStream类中继承于PutField类的一个默认子类实现,接下来分析它的源码去理解put方法的用法,ObjectOutputStream负责的序列化是将Java对象写入到字节流,那么该默认实现就是以此为基础。 -
- private final ObjectStreamClass desc;
-
- private final byte[] primVals;
-
- 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.ObjectStreamClass,PutFieldImpl实现类会从该类的元数据中提取成员属性信息;一方面desc引用会赋值该成员属性desc,然后通过类的元数据中的字段信息初始化primVals字节数组以及objVals对象数组。区分ObjectStreamField类和PutFieldImpl类对字段元数据的描述:ObjectStreamField描述的是成员属性的元数据信息【字段定义】,而PutFieldImpl描述的是成员属性的数据信息【字段使用】。
除了已过期的write方法,PutFieldImpl实现类中提供了另外两个新定义的成员函数
writeFields和getFieldOffset:
- 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 该方法负责将基础类型数据的值和对象类型数据的值写入字节流,看看它的实现细节:
- 先将被解析类中基础类型数据的数量信息写入到字节缓冲区,写入字节流的时候调用了ObjectOutputStream主类的bout成员:
- bout.write(primVals, 0, primVals.length, false);
- 调用desc成员属性的getFields方法获得被解析类中的所有字段元数据信息,注意getFields方法的参数为false,它表示在获取ObjectStreamField的时候,里面的每一个元素引用指向的字段元数据对象是原始对象,而不是一个副本【拷贝】:
- ObjectStreamField[] fields = desc.getFields(false);
- 将字段中的数据写入到字节流中,这段代码调用了主类中的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包中使用。该类中这些方法的第二个参数都是偏移量,主要负责从字节数组中准确写入或者读取某一段字节数据。 这个内部类主要负责调试在序列化过程中的对象状态信息,主要提供该开发人员Debug用。 成员属性 - private final List<String> stack;
- stack——java.util.List<String> 该成员保存了所有调试过程中的堆栈信息,使用的数据类型为一个String列表;
因为该类中定义的方法都比较简单,所以这里直接提供它的完整代码定义,读者自行理解: - private static class DebugTraceInfoStack {
- private final List<String> 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();
- }
- }
一个轻量级的哈希表,它表示一个从对象到整数类型的handle的一个映射,这个地方不知道读者是否还记得前文提到过的接口常量ObjectStreamConstants.baseWireHandle,在字节流中一个引用handle的表示方式是表示成一个int类型的整数,占用四个字节的长度,而HandleTable做的事情就是将Java对象和Handle之间的关系存储起来。这个类的结构复杂,我们拆开来看。 -
- private int size;
-
- private int threshold;
-
- private final float loadFactor;
-
- private int[] spine;
-
- private int[] next;
-
- 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 该方法用于将当前哈希表清空,它调用了Arrays的fill方法,并且设置成员函数size为0;因为该函数调用的时候,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;
一个轻量级的哈希表,用于标识从一个当前对象到“替换对象”的映射关系,这个类中有一个成员htab【HandleTable类型】,它的整体定义如下: - private static class ReplaceTable {
-
-
- private final HandleTable htab;
-
- private Object[] reps;
-
-
-
-
- ReplaceTable(int initialCapacity, float loadFactor) {
- htab = new HandleTable(initialCapacity, loadFactor);
- reps = new Object[initialCapacity];
- }
-
-
-
-
- void assign(Object obj, Object rep) {
- int index = htab.assign(obj);
- while (index >= reps.length) {
- grow();
- }
- reps[index] = rep;
- }
-
-
-
-
-
- Object lookup(Object obj) {
- int index = htab.lookup(obj);
- return (index >= 0) ? reps[index] : obj;
- }
-
-
-
-
- void clear() {
- Arrays.fill(reps, 0, htab.size(), null);
- htab.clear();
- }
-
-
-
-
- int size() {
- return htab.size();
- }
-
-
-
-
- private void grow() {
- Object[] newReps = new Object[(reps.length << 1) + 1];
- System.arraycopy(reps, 0, newReps, 0, reps.length);
- reps = newReps;
- }
- }
这个类中大部分方法调用的都是HandleTable的方法,它包含了许多基于哈希表映射的基本方法,就不重复解析了,请读者结合HandleTable对应的方法自行阅读理解。 在ObjectOutputStream类中,最重要的一个内部类要数BlockDataOutputStream类,这个类负责将缓冲区中的数据写入到字节流。它可以使用两种模式写入数据到字节流: a.在默认模式下,写入数据的时候使用和DataOutputStream相同的模式;java.io.DataOutputStream是JVM中负责将一个基础类型数据写入到输出流中的类,该类称为基础类型数据输出流,只是该类不仅仅使用于Java序列化,如果是流数据的读写,也可以使用java.io.DataOutputStream和java.io.DataInputStream两个类,因为它们的内部结构相对简单,本文不解析这两个类的源代码。 b.使用“Data Block”模式时,写入数据的时候会使用Data Block标记按照数据块的方式写入; - <span style="font-size:12px;"> private static class BlockDataOutputStream extends OutputStream implements DataOutput</span>
这个类的定义和主类的定义有些相似,唯独不同的就是实现的接口。 -
- private static final int MAX_BLOCK_SIZE = 1024;
-
- private static final int MAX_HEADER_SIZE = 5;
-
- private static final int CHAR_BUF_SIZE = 256;
-
-
- private final byte[] buf = new byte[MAX_BLOCK_SIZE];
-
- private final byte[] hbuf = new byte[MAX_HEADER_SIZE];
-
- private final char[] cbuf = new char[CHAR_BUF_SIZE];
-
-
- private boolean blkmode = false;
-
- private int pos = 0;
-
-
- private final OutputStream out;
-
- 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)) {
- drain();
- out.write(b, off, len);
- return;
- }
-
- while (len > 0) {
- if (pos >= MAX_BLOCK_SIZE) {
- drain();
- }
- if (len >= MAX_BLOCK_SIZE && !copy && pos == 0) {
-
- 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,如果越过了直接调用dout的write*对应方法,未越过的情况针对不同数据类型分别写入,并且修改缓冲区中的偏移量——注:每写入一个数据其偏移量的改变值会根据数据类型来,所以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 {
- 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类中的成员属性信息:
-----------------哈希表和输出流-------------
-
- private final BlockDataOutputStream bout;
-
- private final HandleTable handles;
-
- private final ReplaceTable subs;
- bout——java.io.ObjectOutputStream.BlockDataOutputStream 该成员为ObjectOutputStream中的“潜在输出流”,它则以Data Block的方式写入数据到字节流;
- handler——java.io.ObjectOutputStream.HandleTable 该成员为一个哈希表,它表示从对象到引用的映射;
- subs——java.io.ObjectOutputStream.RepalceTable 该成员为一个哈希表,它表示从对象到“替换对象”的一个映射关系;
-----------------基础信息-------------
-
- private int protocol = PROTOCOL_VERSION_2;
-
- private int depth;
-
- private byte[] primVals;
- protocol——int 当前序列化使用的字节流协议的版本号;
- depth——int 需要递归的深度,这个属性主要用于有多个父类的情况,如果出现了由上至下或由下至上的递归的时候使用;
- primVals——byte[] 当前对象中基础类型的字段的值数据存储的字节数组;
-----------------特殊信息-------------
-
- private final boolean enableOverride;
-
- private boolean enableReplace;
-
-
-
-
-
-
- private SerialCallbackContext curContext;
-
- private PutFieldImpl curPut;
-
- private final DebugTraceInfoStack debugInfoStack;
-
-
-
-
- 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的访问控制器来检查代码的执行权限;
ObjectOutputStream类中定义了两个native的本地方法,该方法用于将指定范围的float单精度浮点数以及double的双精度浮点数转换成字节数据,其定义如下: -
-
-
-
- private static native void floatsToBytes(float[] src, int srcpos,
- byte[] dst, int dstpos,
- int nfloats);
-
-
-
-
- private static native void doublesToBytes(double[] src, int srcpos,
- byte[] dst, int dstpos,
- int ndoubles);
前文中讲过浮点数的字节存储格式,在Java语言里,将浮点数转换成字节格式的数据调用的是这两个本地方法,它的实现部分是C语言的代码。从注释部分可以知道,它的转换也可以调用Java语言中的等价API:Float.floatToIntBits和Double.doubleToLongBits来完成。 因为DataOutput是ObjectOutput接口的父接口,而ObjectOutputStream实现了ObjectOutput接口,所以先看看ObjectOutputStream中实现DataOutput接口的成员函数,因为前文已经分析过了DataOutput接口中定义的成员函数,这里仅仅该处该接口中的方法在ObjectOutputStream中的实现,读者对比下和BlockDataOutputStream中的实现看看其差异,实际上ObjectOutputStream中的实现就是调用了bout【BlockDataOutputStream】中的方法,这里就不重复分析了。 - 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函数【后边说明】清空两个哈希表,最后调用bout的close函数关闭underlying的潜在输出流;
- drain 该方法直接调用bout的drain方法,其作用和BlockDataOutputStream中的drain方法一样;
- reset 该方法的调用将会放弃所有写入到字节流中的对象的状态信息,并且将字节流重置到一个新的ObjectOutputStream对象中,当前的字节流中的偏移量也会自动重置;在写入TC_RESET标记时,会先关闭Data Block模式,等到重置信息写入过后,再将Data Block模式打开;不仅仅如此,如果depth成员属性不为0时,会抛出IOException异常信息;
- public void useProtocolVersion(int version) throws IOException {
- if (handles.size() != 0) {
-
- 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_1和PROTOCOL_VERSION_2两个值,若传入的version值不匹配序列化流协议值,同样抛出IllegalArgumentException异常信息;
- getProtocolVersion 获取当前对象序列化时的流协议版本信息;
- 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成员属性;
-
该方法在调用时必须保证curPut成员属性不为null,即上下文环境中已经存在curPut的情况下才能调用该方法,这个方法主要会调用PutFieldImpl中的writeFields方法。在调用writeFields之前会先关闭输出流的Data Block模式,调用之后再开启Data Block模式;
ObjectOutputStream类中有许多定义的write*前缀的私有方法,用于写入不同的Java对象到字节流。前文中已经讲过了一系列的TC_*标记,而这里的“单元方法”即表示这个write方法会写入至少一个TC_*标记到目标介质【字节流中】。这里先看看每一个write方法的定义及其含义。 - 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方法】; - private void writeNull() throws IOException {
- bout.writeByte(TC_NULL);
- }
向目标介质中写入一个空引用null,该方法中会使用TC_NULL标记,直接写入TC_NULL标记; - private void writeHandle(int handle) throws IOException {
- bout.writeByte(TC_REFERENCE);
- bout.writeInt(baseWireHandle + handle);
- }
向目标介质中写入字节流中的对象引用Handle,该方法中会使用TC_REFERENCE标记,前文中讨论过TC_REFERENCE的用法;这个方法会先写入TC_REFERENCE标记,随后就写入对应的值——该引用的值使用baseWireHandle和传入的handle进行计算; - 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方法分析得到的结果; - 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标记;看看这个方法的详细过程: - 先写入TC_PROXYCLASSDESC标记信息;
- 如果使用的模式是非共享模式,则需要将desc所表示的类元数据信息插入到引用->对象的映射表中【*:插入位置是下一个位置,这里调用的是assign方法,而不是insert方法。】;
- 再写入当前对象所属类的接口信息:先写入接口数量,其次遍历所有接口写入每一个接口名称;
- 然后需要调用annotateProxyClass方法,在调用该方法之前开启Data Block模式,调用完成过后再关闭Data Block模式;
- 之后再写入TC_ENDBLOCKDATA标记作为当前动态代理类的描述信息的结束;
- 最后再调用writeClassDesc方法去写入当前对象所属类的父类元数据信息,写入时不以“unshared”方式写入;
- private void writeNonProxyDesc(ObjectStreamClass desc, boolean unshared)
- throws IOException
- {
- bout.writeByte(TC_CLASSDESC);
- handles.assign(unshared ? null : desc);
-
- if (protocol == PROTOCOL_VERSION_1) {
-
- 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标记;看看这个方法的详细过程: - 先写入TC_CLASSDESC标记信息;
- 如果使用的模式是非共享模式,则将desc所表示的类元数据信息插入到引用->对象的映射表中;
- 然后根据使用的流协议版本调用不同的write方法: a.如果使用的流协议是PROTOCOL_VERSION_1,则直接调用desc成员的writeNonProxy方法,将当前引用this作为实参传入到writeNonProxy方法; b.如果使用的流协议不是PROTOCOL_VERSION_1,则调用当前类中的writeClassDescriptor方法;
- 然会需要调用annotateClass方法,在调用该方法之前开启Data Block模式,调用完成过后再关闭Data Block模式;
- 之后再写入TC_ENDBLOCKDATA标记作为当前非动态代理类的描述信息的结束;
- 最后调用writeClassDesc方法写入当前对象所属类的父类元数据信息,写入时同样不使用“unshared”方式写入;
- 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_STRING、TC_LONGSTRING标记;看看这个方法的详细过程: - 写入String对象之前,系统同样会判断当前写入方式是否是“unshared”,如果不是“unshared”方式则需要在引用->对象映射中插入当前String对象;
- 其次调用getUTFLength函数获取String字符串的长度,获取该长度时使用UTF-8编码方式获取;
- 使用getUTFLength方法的返回值和0xFFFF比较: 如果大于该值表示当前String对象是一个长字符串对象——先写入TC_LONGSTRING标记,然后写入字符串的长度和内容; 如果小于等于该值表示当前String对象就是一个普通的字符串对象——先写入TC_STRING标记,然后写入字符串的长度和内容;
- 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标记;看看这个方法的详细过程: - 先写入TC_ARRAY标记信息;
- 然后写入这个数组的类描述信息,写入方式使用“unshared”方式;
- 如果使用的模式是非共享模式,则将desc所表示的类元数据信息插入到引用->对象的映射表中;
- 随后获取当前数组中元素的类型: a.如果元素是基础类型——先写入该数组的长度,其次写入这些数组中所有元素的值; b.如果元素是对象类型——先写入该数组的长度,其次调用writeObject0方法写入这些数组中每一个对象元素;
- 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标记;看看这个方法的详细过程: - 先写入TC_ENUM标记信息;
- 然后获取当前类的父类元数据信息判断是否枚举类信息,如果是枚举类则写入枚举类信息,否则写入当前类的父类信息——从JDK 1.5引入了枚举类型过后,这些枚举信息都继承于Enum.class,所以判断一个类是否枚举类使用这种方式判断;
- 如果使用的模式是非共享模式,则将desc所表示的类元数据信息插入到引用->对象的映射表中;
- 最后将调用枚举类型中的name()方法,将枚举类型的字符串字面量以String方法写入字节流;
- 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标记;看看这个方法的详细过程: - 在写入Java对象信息之前,需要先调用ObjectStreamClass检查当前对象是否是一个可序列化对象;
- 其次写入TC_OBJECT标记;
- 随后调用writeClassDesc方法写入当前对象所属类的类描述信息;
- 如果使用的模式是非共享模式,则将desc所表示的类元数据信息插入到引用->对象的映射表中;
- 随后判断当前Java对象的序列化语义: a.如果当前对象不是一个动态代理类并且是实现了外部化的,则调用writeExternalData方法写入对象信息; b.如果当前对象是一个实现了Serializable接口的,则调用writeSerialData方法写入对象信息;
除了上边提及到的write*单元方法以外,该类中还包含了其他几个比较特殊的write单元方法,在特殊情况下会被调用。 - 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对象实现了外部化过后才调用该方法;看看这个方法的详细过程: - 该方法会判断目前使用的字节流协议: a.如果使用的是PROTOCOL_VERSION_1协议,则直接调用可序列化对象中的writeExternal方法; b.如果不是使用的PROTOCOL_VERSION_1协议,则先开启Data Block模式,再调用writeExternal方法,调用过后关闭Data Block模式,并且在最后追加TC_ENDBLOCKDATA标记;
- 注意这个方法有一个切换上下文环境的过程,定义了一个新的SerialCallbackContext类型的引用oldContext用于保存执行之前的环境,在finally块中将当前环境复原——也就是说在调用writeExternal方法时,curContext的值为null;
- 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对象的数据信息,比如字段值和相关引用等,写入的时候会从顶级父类从上至下递归执行;看看这个方法的详细过程: - 在序列化当前对象之前,先从类描述信息中获取ClassDataSlot信息;
- 判断可序列化对象是否重写了writeObject方法: a.如果没有重写该方法,则调用defaultWriteFields方法写入当前对象中的所有字段信息; b.如果重写了该方法,则先开启Data Block模式,再调用writeObject方法,调用过后关闭Data Block模式,并且在最后追加TC_ENDBLOCKDATA标记;
- 注该方法执行过程同样有一个切换上下文环境的过程,读者自诩阅读和curContext相关的代码行就可以理解了;
ObjectOutputStream中定义了两个default的默认方法,用于默认调用。 - 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字段写入到字节流;看看这个方法的详细过程: - 这个方法只能在writeObject方法内调用,如果检查curContext环境发现当前方法的调用不是在writeObject方法中,则抛出NotActiveException异常信息;
- 从上下文环境中获取对象和类描述信息;
- 最后关闭Data Block模式,调用defaultWriteFields方法将字段信息写入到字节流,调用之后再开启Data Block模式;
- private void defaultWriteFields(Object obj, ObjectStreamClass desc)
- throws IOException
- {
-
- 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对象中的字段数据,并且将字段数据写入到字节流;看看这个方法的详细过程: - 该方法会先检查可序列化的语义;
- 然后获取该对象中所有基础类型字段的值,获得过后写入这些基础类型数据到字节流;
- 完成基础类型的字段值写入过程,再调用writeObject0方法写入对象类型的字段的值;
除了上边提到的default和write*单元方法,该类中还包含了许多write*控制方法,这些write*控制方法主要用于根据不同的情况决定调用上边的哪一个write*单元方法,它除了能够控制流程以外,一般没有实质性实现代码。 - protected void writeClassDescriptor(ObjectStreamClass desc)
- throws IOException
- {
- desc.writeNonProxy(this);
- }
该方法可让子类重写,它只是调用了writeNonProxy方法,父类的默认实现传入this参数给这个方法; - 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对象;
- 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方法;
- 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,它会调用另外两个内部方法writeObjectOverride和writeObject0; - protected void writeObjectOverride(Object obj) throws IOException {
- }
该方法的默认实现为空实现,这个方法存在的目的主要是用于子类重写; - private void writeObject0(Object obj, boolean unshared)
- throws IOException
- {
- boolean oldMode = bout.setBlockDataMode(false);
- depth++;
- try {
-
- 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;
- }
-
-
- Object orig = obj;
- Class cl = obj.getClass();
- ObjectStreamClass desc;
- for (;;) {
-
- Class repCl;
- desc = ObjectStreamClass.lookup(cl, true);
- if (!desc.hasWriteReplaceMethod() ||
- (obj = desc.invokeWriteReplace(obj)) == null ||
- (repCl = obj.getClass()) == cl)
- {
- break;
- }
- cl = repCl;
- }
- if (enableReplace) {
- Object rep = replaceObject(obj);
- if (rep != obj && rep != null) {
- cl = rep.getClass();
- desc = ObjectStreamClass.lookup(cl, true);
- }
- obj = rep;
- }
-
-
- if (obj != 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;
- }
- }
-
-
- 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方法的核心方法,主要用于写入对象信息。它的详细过程如下: - 先关闭输出流的Data Block模式,并且将原始模式赋值给变量oldMode;
- 如果当前传入对象在“替换哈希表【ReplaceTable】”中无法找到,则调用writeNull方法并且返回; 如果当前写入方式是非“unshared”方式,并且可以在“引用哈希表【HandleTable】”中找到该引用,则调用writeHandle方法并且返回; 如果当前传入对象是特殊类型Class类型,则调用writeClass方法并且返回; 如果当前传入对象是特殊类型ObjectStreamClass,则调用writeClassDesc方法并且返回;
- 以上条件都不满足时【传入对象不是ObjectStreamClass和Class类型,而且使用了“unshared”方式,“替换哈希表”中没有该记录】,需要检查替换对象,在检查替换对象时需要跳过string/arrays类型,通过循环方式查找“替换对象”; 【*:到这里可以理解Java序列化中的替换对象是什么概念了——前文JVM的序列化规范中提到过“替换”对象的概念,实际上替换对象就是在可序列化方法中重写了writeReplace方法的Java对象。】
- 通过检查成员属性enableReplace的值判断当前对象是否启用了“替换”功能【Replace功能】;
- 对象替换过后,则需要对原始对象进行二次检查,先将替换对象插入到“替换哈希表”中,然后执行和第二步一模一样的检查来检查原始对象;
- 以上执行都完成过后,处理剩余对象类型: 如果传入对象为String类型,调用writeString方法将数据写入字节流; 如果传入对象为Array类型,调用writeArray方法将数据写入字节流; 如果传入对象为Enum类型,调用writeEnum方法将数据写入字节流; 如果传入对象实现了Serializable接口,调用writeOrdinaryObject方法将数据写入字节流; 以上条件都不满足时则抛出NotSerializableException异常信息;
- 执行完上述代码过后,将输出流的Data Block模式还原;
*:这里的代码执行过程可参考JVM序列化规范中的writeObject方法的概念描述,前一章中描述了writeObject的详细概念流程,对比其概念流程和上边writeObject0的代码流程请读者彻底理解ObjectOutputStream中的writeObject方法究竟做了些什么。 - protected void writeStreamHeader() throws IOException {
- bout.writeShort(STREAM_MAGIC);
- bout.writeShort(STREAM_VERSION);
- }
- public void writeUnshared(Object obj) throws IOException {
- try {
- writeObject0(obj, true);
- } catch (IOException ex) {
- if (depth == 0) {
- writeFatalException(ex);
- }
- throw ex;
- }
- }
以“unshared”的方式写入Java对象到字节流,直接调用writeObject0方法将对象数据写入字节流; - protected void annotateClass(Class<?> cl) throws IOException {
- }
- protected void annotateProxyClass(Class<?> cl) throws IOException {
- }
- 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.ObjectOutputStream的writeObject方法会使用一个Object【实现了Serializable接口】作为参数,它允许不可序列化对象被可序列化对象替换; b.当一个子类是一个替换对象,它必须保证序列化替换中的必须替换操作或者这些可替换对象的每一个字段和引用指向的原始对象是兼容的。如果对象类型并不是字段的子类或者数组元素子类,则中断序列化过程抛出异常,这种情况下不会保存该对象; c.这个方法仅仅在第一次遇到同类型对象时调用,所有之后的引用将重定向到新的对象中,这个方法将在执行过后返回原始对象或者被替换的新对象; d.null可以作为一个对象被替换,但是它有可能引起NullReferenceException异常;
- 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;
- 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的无参构造函数,看看两个构造函数的详细流程: - 调用安全管理器SecurityManager,并且使用安全管理器检查权限SUBCLASS_IMPLEMENTATION_PERMISSION;
- 设置成员属性的默认值: bout——null、handles——null、subs——null、enableOverride——true、debugInfoStack——null
- 调用verifySubclass方法验证子类信息;
- 初始化bout成员属性,实例化一个BlockDataOutputStream;
- 初始化handles和subs,创建两个对应的实例;
- 将enableOverride成员属性的值设置成false;
- 调用writeStreamHeader方法写入魔数和序列化版本信息;
- 开启Data Block模式写入信息;
- 如果启用了调试模式,则需要实例化debugInfoStack;
- 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安全管理器检查代码执行权限; - private static boolean auditSubclass(final Class subcl) {
- Boolean result = AccessController.doPrivileged(
- new PrivilegedAction<Boolean>() {
- 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中的安全管理器检查代码执行权限; - private void clear() {
- subs.clear();
- handles.clear();
- }
清空subs【替换哈希表】和handles【引用哈希表】; 转载地址:http://rokws.baihongyu.com/