Java序列化【草案三】1
来源:互联网 发布:mac 有中文有英文 编辑:程序博客网 时间:2024/05/16 09:54
(从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】?
- 详细分析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的输出方法;
- 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<WeakClassKey,Boolean> subclassAudits =
- new ConcurrentHashMap<>();
- /** queue for WeakReferences to audited subclasses */
- 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方法根据参数类型不同而实现重载;
- /** 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.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();
- }
- }
- }
该成员函数根据字段名称和字段类型来获得该字段在所定义的类中的偏移量offset,它有2个参数:
- name——java.lang.String
字段名称 - type——java.lang.Class
字段类型
- 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;
- }
第一种是直接赋值【byte类型、Object类型】
看看为什么?成员属性primVals本身就是一个byte[]类型,这种类型的每一个元素都是一个byte,所以针对byte数据写入的时候,可以使用下边的代码来实现:
- primVals[getFieldOffset(name, Byte.TYPE)] = val;
- objVals[getFieldOffset(name, Object.class)] = val;
类似下边这种代码:
- Bits.putLong(primVals, getFieldOffset(name, Long.TYPE), val);
成员属性
- 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();
- }
- }
- /* 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
该方法用于将当前哈希表清空,它调用了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;
- 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;
- }
- }
a.在默认模式下,写入数据的时候使用和DataOutputStream相同的模式;java.io.DataOutputStream是JVM中负责将一个基础类型数据写入到输出流中的类,该类称为基础类型数据输出流,只是该类不仅仅使用于Java序列化,如果是流数据的读写,也可以使用java.io.DataOutputStream和java.io.DataInputStream两个类,因为它们的内部结构相对简单,本文不解析这两个类的源代码。
- <span style="font-size:12px;"> private static class BlockDataOutputStream extends OutputStream implements DataOutput</span>
- /** 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】;
- BlockDataOutputStream(OutputStream out) {
- this.out = out;
- dout = new DataOutputStream(this);
- }
-----------------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,如果越过了直接调用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 { // 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的访问控制器来检查代码的执行权限;
- /**
- * 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);
- 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);
- }
- 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);
- }
- 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) {
- // 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_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成员属性; - writeFields该方法在调用时必须保证curPut成员属性不为null,即上下文环境中已经存在curPut的情况下才能调用该方法,这个方法主要会调用PutFieldImpl中的writeFields方法。在调用writeFields之前会先关闭输出流的Data Block模式,调用之后再开启Data Block模式;
- 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);
- }
- }
- private void writeNull() throws IOException {
- bout.writeByte(TC_NULL);
- }
- private void writeHandle(int handle) throws IOException {
- bout.writeByte(TC_REFERENCE);
- bout.writeInt(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);
- }
- 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标记信息;
- 如果使用的模式是非共享模式,则需要将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) {
- // 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标记信息;
- 如果使用的模式是非共享模式,则将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);
- }
- }
- 写入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();
- }
- }
- }
- }
- 先写入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);
- }
- 先写入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对象信息之前,需要先调用ObjectStreamClass检查当前对象是否是一个可序列化对象;
- 其次写入TC_OBJECT标记;
- 随后调用writeClassDesc方法写入当前对象所属类的类描述信息;
- 如果使用的模式是非共享模式,则将desc所表示的类元数据信息插入到引用->对象的映射表中;
- 随后判断当前Java对象的序列化语义:
a.如果当前对象不是一个动态代理类并且是实现了外部化的,则调用writeExternalData方法写入对象信息;
b.如果当前对象是一个实现了Serializable接口的,则调用writeSerialData方法写入对象信息;
- 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;
- }
- 该方法会判断目前使用的字节流协议:
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);
- }
- }
- }
- 在序列化当前对象之前,先从类描述信息中获取ClassDataSlot信息;
- 判断可序列化对象是否重写了writeObject方法:
a.如果没有重写该方法,则调用defaultWriteFields方法写入当前对象中的所有字段信息;
b.如果重写了该方法,则先开启Data Block模式,再调用writeObject方法,调用过后关闭Data Block模式,并且在最后追加TC_ENDBLOCKDATA标记; - 注该方法执行过程同样有一个切换上下文环境的过程,读者自诩阅读和curContext相关的代码行就可以理解了;
- 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);
- }
- 这个方法只能在writeObject方法内调用,如果检查curContext环境发现当前方法的调用不是在writeObject方法中,则抛出NotActiveException异常信息;
- 从上下文环境中获取对象和类描述信息;
- 最后关闭Data Block模式,调用defaultWriteFields方法将字段信息写入到字节流,调用之后再开启Data Block模式;
- 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();
- }
- }
- }
- }
- 该方法会先检查可序列化的语义;
- 然后获取该对象中所有基础类型字段的值,获得过后写入这些基础类型数据到字节流;
- 完成基础类型的字段值写入过程,再调用writeObject0方法写入对象类型的字段的值;
- protected void writeClassDescriptor(ObjectStreamClass desc)
- throws IOException
- {
- desc.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引用是一个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;
- }
- }
- protected void writeObjectOverride(Object obj) throws IOException {
- }
- 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);
- }
- }
- 先关闭输出流的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模式还原;
- 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;
- }
- }
- protected void annotateClass(Class<?> cl) throws IOException {
- }
- protected void annotateProxyClass(Class<?> cl) throws IOException {
- }
- protected Object replaceObject(Object obj) throws IOException {
- return obj;
- }
- 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;
- }
- 调用安全管理器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);
- }
- 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();
- }
- private void clear() {
- subs.clear();
- handles.clear();
- }
- Java序列化【草案三】1
- Java序列化【草案三】
- 21.Java序列化【草案三】
- Java序列化【草案三】2
- Java序列化【草案一】
- Java序列化【草案二】
- Java序列化【草案一】
- Java序列化【草案二】
- 19.Java序列化【草案一】
- 20.Java序列化【草案二】
- Java引用总结【草案】
- Java基本数据类型【草案】
- Java引用总结【草案】
- Java引用总结【草案】
- Java引用总结【草案】
- Java引用总结【草案】
- IO、文件、NIO【草案三】
- 【Java基础之三】Java序列化
- Linux下rar文件如何解压
- uva 297 Quadtrees
- 设备驱动-----Android关机流程总结
- 10岁男孩不写作业担心挨打 打110称遭父母虐待
- Spfa用法简介
- Java序列化【草案三】1
- 从JVM内存管理的角度谈谈静态方法和静态属性 和 java对象引用与JVM自动内存管理
- 大佛顶首楞严经摄论 太虚大师著 -读记
- NPN、PNP型三极管的常见问题
- arm assembly language
- Java序列化【草案三】2
- 相位相关法介绍
- hdu1754
- 网络编程之 keepalive