JDK序列化机制及源码解读二:ObjectOutputStream对象输出流

这是Java序列化机制及源码解读系列的第二篇,主要学习源码处理对象输出流的方法。接下来会先学习ObjectOutputStream的父类,然后再探究ObjectOutputStream。

1
2
public class ObjectOutputStream
extends OutputStream implements ObjectOutput, ObjectStreamConstants

ObjectOutputStream UML图

学习ObjectOutputStream前,先了解下其父类。

一、DataOutput

DataOutput接口提供将数据从任何Java基本类型(byte、short、int、long、float、double、char、boolean)转换为一系列字节,并将这些字节写入二进制流。 还有一种将String转换为modified UTF-8格式并编写结果字节系列的功能。

DataOutput定义了以下接口方法:

Modifier and Type Method and Description
void write(byte[] b):将输出流写入数组 b中的所有字节。
void write(byte[] b, int off, int len):从阵列 b写入 len字节,以输出流。
void write(int b):向输出流写入参数 b的八个低位。
void writeBoolean(boolean v):将 boolean值写入此输出流。
void writeByte(int v):向输出流写入参数 v的八个低位位。
void writeBytes(String s):将一个字符串写入输出流。
void writeChar(int v):将两个字节组成的 char值写入输出流。
void writeChars(String s):写入每一个字符在字符串中 s ,到输出流中,为了,每个字符使用两个字节。
void writeDouble(double v):将 double值(由8个字节组成)写入输出流。
void writeFloat(float v):将 float值写入输出流,该值由四个字节组成。
void writeInt(int v):将 int值(由四个字节组成)写入输出流。
void writeLong(long v):将 long值(由八个字节组成)写入输出流。
void writeShort(int v):将两个字节写入输出流以表示参数的值。
void writeUTF(String s):将两个字节的长度信息写入输出流,其后是 字符串 s中每个字符的 s 。

二、ObjectOutput

ObjectOutput扩展了DataOutput接口,除了原始类型外还添加了对象、数组和字符串的输出流。

Modifier and Type Method and Description
void close():关闭流。
void flush():刷新流。
void write(byte[] b):写入一个字节数组。
void write(byte[] b, int off, int len):写入一个子字节数组。
void write(int b):写一个字节。
void writeObject(Object obj):将对象写入底层存储或流。

三、OutputStream

这个抽象类是表示字节输出流的所有类的超类。输出流接收输出字节并将其发送到某个接收器。

需要定义OutputStream子类的应用OutputStream必须至少提供一个写入一个字节输出的方法。

Modifier and Type Method and Description
void close():关闭此输出流并释放与此流相关联的任何系统资源。
void flush():刷新此输出流并强制任何缓冲的输出字节被写出。
void write(byte[] b):将 b.length字节从指定的字节数组写入此输出流。
void write(byte[] b, int off, int len):从指定的字节数组写入 len个字节,从偏移 off开始输出到此输出流。
void write(int b):将指定的字节写入此输出流。
void writeObject(Object obj):将对象写入底层存储或流。

四、ObjectStreamConstants

ObjectStreamConstants定义了一系列的常量,用来表示对象进行序列化时单个字节数据的含义,方便后续序列化和反序列化按照此规范进行。

Modifier and Type Field Description Value
static short STREAM_MAGIC 魔术头,标记序列化协议的信息 0xaced
static short STREAM_VERSION 标记序列化协议的版本信息 0x0005
static byte TC_BASE 第一个标签值 0x70
static byte TC_NULL Null对象引用,父类描述符标识 0x70
static byte TC_REFERENCE 流中已存在对象的引用 0x71
static byte TC_CLASSDESC new class类描述符 0x72
static byte TC_OBJECT new 对象 0x73
static byte TC_STRING new String字符串 0x74
static byte TC_ARRAY new 数组 0x75
static byte TC_CLASS 类引用 0x76
static byte TC_BLOCKDATA 字节跟随标记表示此块数据中的字节数 0x77
static byte TC_ENDBLOCKDATA 一个对象的可选数据块的结尾 0x78
static byte TC_RESET 重置流上下文 0x79
static byte TC_BLOCKDATALONG long类型数据块,标记long类型数据块的数量 0x7A
static byte TC_EXCEPTION 写过程发生异常 0x7B
static byte TC_LONGSTRING long字符串 0x7C
static byte TC_PROXYCLASSDESC new 代理类描述符 0x7D
static byte TC_ENUM new 枚举常量 0x7E
static byte TC_MAX 最后一个标签值 0x7E
static int baseWireHandle 分配的第一个句柄 0x7e0000
static byte SC_WRITE_METHOD 对于objectstreamclass标志位屏蔽。表示一个可序列化的类自定义了writeObject() 0x01
static byte SC_SERIALIZABLE 对于objectstreamclass标志位屏蔽。显示类的序列化,实现Serializable 0x02
static byte SC_EXTERNALIZABLE 对于objectstreamclass标志位屏蔽。指示类是外部的,实现Externalizable 0x04
static byte SC_BLOCK_DATA 对于objectstreamclass标志位屏蔽。说明写的块数据模式的外部数据 0x08
static byte SC_ENUM 对于objectstreamclass标志位屏蔽。指出类是一个枚举类型,Enum 0x10
static int PROTOCOL_VERSION_1 流协议的版本 0x0001
static int PROTOCOL_VERSION_2 流协议的版本 0x0002
static SerializablePermission SUBCLASS_IMPLEMENTATION_PERMISSION 允许重写 readObject 和 writeObject new SerializablePermission(“enableSubclassImplementation”);
static SerializablePermission SUBSTITUTION_PERMISSION 允许在序列化/反序列化期间使用一个对象取代另一个对象。 new SerializablePermission(“enableSubstitution”);

五、ObjectOutputStream

ObjectOutStream有7个内部类,其作用如下:

  1. BlockDataOutputStream Java基本数据类型的写入流实现
  2. Caches 用于安全审计缓存
  3. DebugTraceInfoStack 堆栈保存有关序列化进程状态的调试信息,以便嵌入异常消息
  4. HandleTable 保存对象及其句柄的映射关系 作用是缓存写过的共享class便于下次查找
  5. ReplaceTable 替换对象的映射关系
  6. PutField和PutFieldImpl 动态修改序列化的字段

先着重分析下BlockDataOutputStream,再分析ObjectOutputStream的方法及序列化流程。

1、静态内部类BlockDataOutputStream-Java基本数据类型的写入流实现

1
2
private static class BlockDataOutputStream
extends OutputStream implements DataOutput

BlockDataOutputStream实现了DataOutput并且继承了OutputStream,该类主要用来将数据从Java基本类型转换字节流并写入底层数据流。

缓冲输出流有两种模式:在默认模式下,输出数据和DataOutputStream使用同样模式;在块数据模式下,使用一个缓冲区来缓存数据到达最大长度或者手动刷新时将内容写入底层数据流,块模式在写数据之前,要先写入一个头部来表示当前块的长度。

从内部变量和构造函数中可以看出,缓冲区的大小是固定且不可修改的,其中包含了一个下层输入流和一个数据输出流以及是否采用块模式的标识,在构造时默认不采用块数据模式。

1.1、BlockDataOutputStream构造方法:

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
private static class BlockDataOutputStream extends OutputStream implements DataOutput
{
/** maximum data block length */
// 缓冲数据块长度1024
private static final int MAX_BLOCK_SIZE = 1024;
/** maximum data block header length */
// 头部最大长度5
private static final int MAX_HEADER_SIZE = 5;
/** (tunable) length of char buffer (for writing strings) */
// 针对string类型 :char字符缓冲块长度256
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 */
// 在字节数据缓冲区的当前偏移量,初始为0
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,块数据模式默认关闭
BlockDataOutputStream(OutputStream out) {
this.out = out;
dout = new DataOutputStream(this);
}

1.2、块数据模式及底层流数据写入

BlockDataOutputStream定义了两个方法用于设置和获取块数据模式:setBlockDataMode可以改变当前的数据模式,从块数据模式切换到非块数据模式时,要将缓冲区内的数据写入到底层流;getBlockDataMode查询当前块数据模式。

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
/**
* 设置块数据模式为给出的模式true是开启,false是关闭,并返回之前的模式值。
* 如果新的模式和旧的一样,什么都不做。
* 如果新的模式和旧的模式不同,所有的缓冲区数据要在转换到新模式之前刷新。
*/
boolean setBlockDataMode(boolean mode) throws IOException {
if (blkmode == mode) {
return blkmode;
}
// 将缓冲区内的数据全部写入底层流
drain();
blkmode = mode;
return !blkmode;
}
// 当前流为块数据模式返回true,否则返回false
boolean getBlockDataMode() {
return blkmode;
}

drain在多个方法中被调用,作用是将缓冲区内的数据全部写入底层流,但不会刷新底层流,在写入实际数据前要先用writeBlockHeader写入块头部,头部包含1字节标识位和1字节或4字节的长度大小

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
// 将缓冲数据写入到底层流,但是不刷新底层流
void drain() throws IOException {
// 空缓冲区
if (pos == 0) {
return;
}
// 块数据模式头部数据写入
if (blkmode) {
writeBlockHeader(pos);
}
// 写入缓冲区数据
out.write(buf, 0, pos);
// 重置缓冲区偏移
pos = 0;
}

// 写块数据的头部信息,数据块少于256字节 头部写入2个字节,多于256字节则写入5个字节的头部
// 因为255字节以内可以用1字节来表示长度,4字节可以表示int范围内的最大整数
private void writeBlockHeader(int len) throws IOException {
// 小于256,TC_BLOCKDATA 0x77标记字节跟随标记表示此块数据中的字节数
if (len <= 0xFF) {
hbuf[0] = TC_BLOCKDATA;
hbuf[1] = (byte) len;
out.write(hbuf, 0, 2);
} else {
// 大于255,TC_BLOCKDATALONG 0x7A标记long类型数据块
hbuf[0] = TC_BLOCKDATALONG;
Bits.putInt(hbuf, 1, len);
out.write(hbuf, 0, 5);
}
}

通用数据流写入的处理方法:write主要是写入到缓冲区,写入前都需要先检查缓冲区有没有达到上限,达到时需要先刷新,然后再将数据复制到缓冲区。

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
67
68
/* ----------------- 通用数据流处理方法 ----------------- */
/*
* 下面的方法 除了在参与块数据模式下写入数据到数据块中的部分有所不同
* 其它都等价于他们在OutputStream中的对应方法
*/

//存储一个字节的数据到缓冲区,如果缓冲区满了先刷新缓冲区写入底层流
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);
}

// 从给定数组写入指定的字节值范围。如果copy为true,则在将值写入底层流之前将其复制到中间缓冲区(以避免暴露对原始字节数组的引用
void write(byte[] b, int off, int len, boolean copy)
throws IOException
{
// 非copy模式也非块数据模式直接将数据写入底层流
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) {
// 长度大于缓冲区 非copy模式 且缓冲区为空直接写,避免不必要的复制
writeBlockHeader(MAX_BLOCK_SIZE);
out.write(b, off, MAX_BLOCK_SIZE);
off += MAX_BLOCK_SIZE;
len -= MAX_BLOCK_SIZE;
} else {
// 剩余内容在缓冲区内放得下 或者缓冲区不为空 或者是copy模式,则将数据复制到缓冲区
int wlen = Math.min(len, MAX_BLOCK_SIZE - pos);
System.arraycopy(b, off, buf, pos, wlen);
pos += wlen;
off += wlen;
len -= wlen;
}
}
}

// 刷新缓冲区并且刷新底层流
public void flush() throws IOException {
drain();
out.flush();
}

//刷新后关闭流
public void close() throws IOException {
flush();
out.close();
}

1.3、BlockDataOutputStream处理基元数据输出方法:

剩余写不同基元数据的方法到缓冲区都类似,write[Type]和write[Type]s一个是处理单个基元数据和一组连续基元数据的情况,write[Type]s相对来说更加优化,以便更有效地写入一组基元数据值或者处理写基元类型的数组,其优化的方式是先计算出缓冲区内的剩余大小,计算可以写入的个数,然后直接写入而不是每次写入之前检查缓冲区是否有空间,减少判断次数。

下面以writeInt和writeInts对Int类型的写入方法深入探究下,其它方法类似不再赘诉。

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
/* ----------------- 基元数据输出方法 ----------------- */
// 基元数据输出方法方法与DataOutputStream中的对应方法等效,只是在块数据模式下,它们将写入的数据写入缓冲区。基本上逻辑都是先检查空间是否足够,不足的话先刷新缓冲区,然后将数据存储到缓冲区中。写一个字符串时,需要先将字符串中的字符存储到字符缓冲数组中,然后再转换成字节存储到buf中。

public void writeInt(int v) throws IOException {
//先检查缓冲区是否达到上限,注意一个Int值占4个字节
if (pos + 4 <= MAX_BLOCK_SIZE) {
// Bits.putInt将Int值转为字节存储到字节缓冲区
Bits.putInt(buf, pos, v);
pos += 4;
} else {
// DataOutputStream.writeInt达到缓冲区将int作为4个字节写入底层输出流
dout.writeInt(v);
}
}

/* -------------- 连续多个基元数据输出方法 -------------- */
//下面的方法写出基元数据值的范围。虽然相当于重复调用相应的基元写方法,但这些方法都经过了重新优化,以便更有效地写入一组基元数据值。
//优化的方式是先计算出缓冲区内的剩余大小,计算可以写入的个数,然后直接写入而不是每次写入之前检查缓冲区是否有空间,减少判断次数。
void writeInts(int[] v, int off, int len) throws IOException {
// 缓冲区上限
int limit = MAX_BLOCK_SIZE - 4;
// 基元数据值的范围,最终偏移位置
int endoff = off + len;
// 连续写入多个int数据
while (off < endoff) {
if (pos <= limit) {
// 除以2的2次方,int是4个字节,所以除以4,计算缓冲区剩余有效int的个数
int avail = (MAX_BLOCK_SIZE - pos) >> 2;
// 比较最终偏移位置和当前偏移位置+缓冲区剩余有效int个数 取最小值
int stop = Math.min(endoff, off + avail);
while (off < stop) {
// 未达缓冲区上限,连续存储数据到缓冲区,减少了判断缓冲区是否满的次数
// Bits.putInt将int类型转为字节存储到字节缓冲区
Bits.putInt(buf, pos, v[off++]);
pos += 4;
}
} else {
// 达到缓冲区上限
// DataOutputStream.writeInt用于将int数据作为4个字节写入底层输出流,高字节优先
dout.writeInt(v[off++]);
}
}
}

除了write[Type]和write[Type]s,还有writeUTF 写UTF8格式:

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
// 以UTF格式写入给定的字符串。此方法用于字符串的UTF编码长度已经已知的情况;显式地指定它可以避免对字符串进行预扫描以确定其UTF长度。
void writeUTF(String s, long utflen) throws IOException {
if (utflen > 0xFFFFL) {
throw new UTFDataFormatException();
}
// 写字符串长度,两个字节
writeShort((int) utflen);
if (utflen == (long) s.length()) {
// 如果要写入的长度就是字符串s的长度,直接用writeBytes(s)写入
writeBytes(s);
} else {
//否则 用writeUTFBody写入
writeUTFBody(s);
}
}

2、序列化导读

在开始分析writeObject前,再来强调下,Java序列化时只能序列化该对象的类信息、属性及其值,不能序列化对象的方法,JAVA序列化了类信息以后也就可以通过类调用其方法了。所以最终writeObject序列化后的结果一定是类信息和属性的序列化。

为了方便后面writeObject的分析,先看下一个序列化的例子:

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
package serial;

import java.io.*;

//要序列化的类必须实现Serializable
class TestSerializable implements Serializable{
// 设置serialVersionUID
private static final long serialVersionUID = -6211228684695072792L;
private int intField = 112;
private boolean boolField = false;
private short shortField = 12;
private String stringField = "stringvalue";
private File fileField = new File("E:\\");

//任意方法,可忽略
public final void test() {
System.out.println("test");
}
}

// 序列化测试:这里只有序列化,不测试反序列化
public class Main {
public static void main(String[] args) throws Exception {
/* 序列化对象:由对象转为字节流 */
TestSerializable testSerializable = new TestSerializable();
//创建 ObjectOutputStream 对象输出流,最终输出流到文件
ObjectOutputStream oos = new ObjectOutputStream(new FileOutputStream(new File("e:\\test.txt")));
//写入对象到输出流
oos.writeObject(testSerializable);
System.out.println("对象序列化成功!");
oos.flush();
oos.close();
}
}

序列化后的文件输出结果如下图:

image-20210630210013254

虽然序列化后的信息看起来比较乱,但是还是能得到一些信息的

  • 类的全称serial.TestSerializable
  • 属性类型及属性名和ZboolField(Z代表boolean类型,后续会提到)
  • 属性类型及属性名IintField(I代表Int类型,后续会提到)
  • 属性类型及属性名SshortField(S代表short类型,后续会提到)
  • 属性名及属性类型fileFieldjava/io/File
  • 属性名及属性类型stringFieldjava/lang/String
  • 属性值E:\stringvalue(当然还有其他值这里只说能看到的)

备注:后续分析时会将上面例子带入分析,以(例子分析:)形式备注在每一段分析的最后面。

3、ObjectOutputStream构造方法

3.1、有参构造:

首先把bout绑定到底层的字节数据容器,调用writeStreamHeader()方法完成序列化流头部信息的写入0xaced0005

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
public ObjectOutputStream(OutputStream out) throws IOException {
verifySubclass();
// 创建一个块数据输出流
bout = new BlockDataOutputStream(out);
// 创建一个HandleTable和ReplaceTable
handles = new HandleTable(10, (float) 3.00);
subs = new ReplaceTable(10, (float) 3.00);
// Override标识
enableOverride = false;
// 写入流数据头
writeStreamHeader();
// 启用块数据模式
bout.setBlockDataMode(true);
if (extendedDebugInfo) {
//创建异常处理
debugInfoStack = new DebugTraceInfoStack();
} else {
debugInfoStack = null;
}
}

3.2、writeStreamHeader():

写入魔术头和版本信息

1
2
3
4
5
protected void writeStreamHeader() throws IOException {
// ObjectStreamConstants定义的常量
bout.writeShort(STREAM_MAGIC); // ac ed
bout.writeShort(STREAM_VERSION); // 00 05
}

(例子分析:此时上面的例子写入了ac ed 00 05

4、writeObject写入对象输出流

writeObject的调用

writeObject的调用

4.1、writeObject:

writeObject

如果writeObject被重写会调用writeObjectOverride重写方法,否则调用 writeObject0方法

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
public final void writeObject(Object obj) throws IOException {
//if true, invoke writeObjectOverride() instead of writeObject()
if (enableOverride) {
//如果流子类重写了writeObject则调用writeObjectOverride
writeObjectOverride(obj);
return;
}
try {
//其它情况则调用writeObject0
writeObject0(obj, false);
} catch (IOException ex) {
if (depth == 0) {
// 如果序列化出现异常调用 writeFatalException 方法
writeFatalException(ex);
}
throw ex;
}
}
writeFatalException:处理序列化异常情况下的序列化信息
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
// 尝试写入导致序列化中止的致命IOException信息到底层流。
private void writeFatalException(IOException ex) throws IOException {
clear();
// 关闭块数据模式
boolean oldMode = bout.setBlockDataMode(false);
try {
// 将异常标识TC_EXCEPTION 0x7B写入底层流
bout.writeByte(TC_EXCEPTION);
// 将IOException异常对象序列化
writeObject0(ex, false);
clear();
} finally {
//重启块数据模式
bout.setBlockDataMode(oldMode);
}
}
writeObject0:

序列化的核心方法,主要有三个步骤,1是处理不需要序列化的情况、2是判断是否替换了对象(有writeReplace方法)、3是处理其它情况 主要是String|Array|Enum和一般可序列化对象的序列化。

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
67
68
69
70
71
72
73
74
75
76
77
78
79
80
81
82
83
84
85
86
87
88
89
90
91
92
93
94
95
96
97
98
99
100
101
102
103
104
105
106
107
private void writeObject0(Object obj, boolean unshared) throws IOException
{
//关闭块数据模式
boolean oldMode = bout.setBlockDataMode(false);
// 递归深度,表示writeObject0被调用的深度
// 比如序列化 A 对象时调用 writeObject 则 depth++,而 A 对象的字段又是一个对象,此时又会递归调用 writeObject 方法,当 writeObject 方法执行完成时 depth--。因而如果不出异常则 depth 最终会是 0,有异常则在 catch 模块时 depth 不为 0。
depth++;
try {
// handle previously written and non-replaceable objects
int h;
/**** 1、 不需要序列化的情况 ***/
if ((obj = subs.lookup(obj)) == null) {
//替换后的对象为 null
writeNull();
return;
} else if (!unshared && (h = handles.lookup(obj)) != -1) {
//handles存储的是已经序列化的对象句柄,如果找到了,直接写一个句柄就可以了
writeHandle(h);
return;
} else if (obj instanceof Class) {
//Class对象
writeClass((Class) obj, unshared);
return;
} else if (obj instanceof ObjectStreamClass) {
//ObjectStreamClass 序列化类的描述信息
writeClassDesc((ObjectStreamClass) obj, unshared);
return;
}

/*** 2、判断是否替换了对象 ***/
Object orig = obj;
// 获取要序列化的对象的Class对象
Class<?> cl = obj.getClass();
// 创建一个类的序列化描述符对象。
ObjectStreamClass desc;
// 1. 如果要序列化的对象中有 writeReplace 方法,则递归检查最终要输出的对象
for (;;) {
// REMIND: skip this check for strings/arrays?
Class<?> repCl;
// 创建描述序列化对象Class的ObjectStreamClass对象
desc = ObjectStreamClass.lookup(cl, true);
// 如果要序列化的对象中有 writeReplace 方法,则递归检查最终要输出的对象
if (!desc.hasWriteReplaceMethod() ||
(obj = desc.invokeWriteReplace(obj)) == null ||
(repCl = obj.getClass()) == cl)
{
break;
}
cl = repCl;
}

// 子类重写了replaceObject方法
if (enableReplace) {
//enableReplace和写入时替换对象writeReplace方法有关,如果自定义writeReplace,则调用replaceObject
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
// 既然要序列化的对象已经被替换了,此时就需要再次做判断,和步骤1类似
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
//*** 3、处理其余情况:String、Array、Enum及实现了Serializable的对象的写入输出流 ***/
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) {
// 处理Serializable接口的普通对象
writeOrdinaryObject(obj, desc, unshared);
} else {
// 如果没有实现Serializable接口会抛出 NotSerializableException异常
if (extendedDebugInfo) {
throw new NotSerializableException(
cl.getName() + "\n" + debugInfoStack.toString());
} else {
throw new NotSerializableException(cl.getName());
}
}
} finally {
depth--;
//刷新块数据模式
bout.setBlockDataMode(oldMode);
}
}

(例子分析:此时新写入任何数据,还是ac ed 00 05,按照示例代码,接下来会进入writeOrdinaryObject方法)

补充:ObjectStreamClass

在上面writeObject0代码,创建了一个用于类序列化描述符的ObjectStreamClass对象,我们先大体分析下ObjectStreamClass。

官方文档是这样描述这个类的:

类的序列化描述符。 它包含该类的名称和serialVersionUID。 以使用lookup方法查找/创建Java Java中加载的特定类的ObjectStreamClass。

除了内部类外它具有以下方法:

1
2
3
4
5
6
7
8
forClass():返回此版本映射到的本地VM中的类。
getField(String name):通过名称获取此类的字段。
getFields():返回此可序列化类的字段数组。
getName():返回此描述符描述的类的名称。
getSerialVersionUID():返回此类的serialVersionUID。
lookup(类<?> cl):找到可以序列化的类的描述符。
lookupAny(类<?> cl):返回任何类的描述符,无论它是否实现Serializable。
toString():返回描述此ObjectStreamClass的字符串。

通过以上方法ObjectStreamClass对象可以获取Class、类名称、类属性数组、类的serialVersionUID等。

了解了ObjectStreamClass,继续回到我们的分析。

三种类型的序列化 write[String|Array|Enum]:
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
67
68
69
70
71
72
73
74
75
76
77
78
79
80
81
82
83
84
85
86
87
88
89
90
91
92
93
94
95
96
97
98
99
100
101
102
103
//写字符串到底层流即字符串的序列化:格式 类型标识0x74/0x7C+内容
private void writeString(String str, boolean unshared) throws IOException {
handles.assign(unshared ? null : str);
long utflen = bout.getUTFLength(str);
// 长度小于 0xFFFF(65506)
if (utflen <= 0xFFFF) {
bout.writeByte(TC_STRING); //写标识0x74
bout.writeUTF(str, utflen); //writeUTF写长度+内容
} else {
bout.writeByte(TC_LONGSTRING);//写标识0x7C
bout.writeLongUTF(str, utflen);//长度+写内容
}
}

// 写数组到底层流 即数组的序列化
private void writeArray(Object array, ObjectStreamClass desc, boolean unshared) throws IOException
{
bout.writeByte(TC_ARRAY); //写标识0x75
writeClassDesc(desc, false); //写描述符
handles.assign(unshared ? null : array);

Class<?> ccl = desc.forClass().getComponentType();
//处理基元数据的数组,格式:TC_ARRAY+描述符+长度+基元数据值的byte
if (ccl.isPrimitive()) {
if (ccl == Integer.TYPE) {
int[] ia = (int[]) array;
bout.writeInt(ia.length); //写长度
bout.writeInts(ia, 0, ia.length); //写一组int值,将int转为byte
} 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 {//非基元数据数组,根据数组长度循环调用writeObject0
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处理对象的写入
writeObject0(objs[i], false);
} finally {
if (extendedDebugInfo) {
debugInfoStack.pop();
}
}
}
} finally {
if (extendedDebugInfo) {
debugInfoStack.pop();
}
}
}
}

//写枚举类型的数据
private void writeEnum(Enum<?> en,
ObjectStreamClass desc,
boolean unshared)
throws IOException
{
bout.writeByte(TC_ENUM);//写标识0x7E
ObjectStreamClass sdesc = desc.getSuperDesc();
writeClassDesc((sdesc.forClass() == Enum.class) ? desc : sdesc, false);
handles.assign(unshared ? null : en);
writeString(en.name(), false); //写枚举名称以字符串形式序列化
}
序列化关键部分writeOrdinaryObject:

Java 序列化普通对象保存了三部分的数据:类型信息序列化TC_OBJECT + 类信息序列化 writeClassDesc() + 类实例数据信息序列化。到这里终于可以看到 io 序列化流的操作了。这个方法主要是在 Externalizable 和 Serializable 的接口出现分支,如果实现了 Externalizable 接口并且类描述符非动态代理,则执行 writeExternalData,否则执行 writeSerialData。同时,这个方法会写类描述信息。

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
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();
//写入类型TC_OBJECT 0x73,标识接下来数据是一个新的对象
bout.writeByte(TC_OBJECT);
//写入类信息(类元信息写入结束的标志是TC_ENDBLOCKDATA 0x78,具体参考下一部分writeClassDesc的分析)
writeClassDesc(desc, false);
handles.assign(unshared ? null : obj);
// 写入类实例数据
// Externalizable或者动态代理调用writeExternalData,否则调用writeSerialData
if (desc.isExternalizable() && !desc.isProxy()) {
// 实现Externalizable接口的类对象的序列化
writeExternalData((Externalizable) obj);
} else {
// 实现Serializable接口的类对象的序列化
writeSerialData(obj, desc);
}
} finally {
if (extendedDebugInfo) {
debugInfoStack.pop();
}
}
}

(例子分析:加入TC_OBJECT 0x73,当前流为ac ed 00 05 73,接下来调用writeClassDesc来写入类信息)

为了方便阅读,下面将根据两部分进行解析,一是类信息序列化 writeClassDesc(),一是类实例数据信息序列化writeExternalData/ writeSerialData。

4.2、类元信息序列化 writeClassDesc()

writeClassDesc-序列化类信息:

writeClassDesc序列化类信息,序列化时会先递归调用 writeClassDesc 方法,将实现 Serializable 接口的父类信息也会同时序列化。类信息都保存在 ObjectStreamClass 类中,同时也可以通过 ObjectStreamClass#getFields 获取所有要序列的字段信息 ObjectStreamField。

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
// 将给定类描述符的表示形式写入流
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);
}
}

// 将表示标准(非动态代理)类的类描述符写入流。
private void writeNonProxyDesc(ObjectStreamClass desc, boolean unshared) throws IOException
{
bout.writeByte(TC_CLASSDESC); //写0x72,代表一个新的类描述符
handles.assign(unshared ? null : desc);

if (protocol == PROTOCOL_VERSION_1) {
// do not invoke class descriptor write hook with old protocol
desc.writeNonProxy(this);
} else {
// 写入类信息,本质上也是调用 desc.writeNonProxy(this)
writeClassDescriptor(desc);
}

Class<?> cl = desc.forClass();
//刷新块数据
bout.setBlockDataMode(true);
if (cl != null && isCustomSubclass()) {
ReflectUtil.checkPackageAccess(cl);
}
annotateClass(cl);
bout.setBlockDataMode(false);
//注意这里有一个块数据结束标识的写入 TC_ENDBLOCKDATA 0x78
bout.writeByte(TC_ENDBLOCKDATA);

writeClassDesc(desc.getSuperDesc(), false); // 递归调用
}

(例子分析:加入TC_CLASSDESC 0x72,当前流为ac ed 00 05 73 72,接下来调用writeNonProxy来写入类信息)

ObjectStreamClass#writeNonProxy:写入实际的类元信息
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
// 将非代理类描述符信息写入给定的输出流。
void writeNonProxy(ObjectOutputStream out) throws IOException {
out.writeUTF(name); //写入类的名字 (例子分析:写入serial.TestSerializable,writeUTF会先写入长度再写字符串,长度为23 0x0017,serial.TestSerializable十六进制是`73 65 72 69 61 6c 2e 54 65 73 74 53 65 72 69 61 6c 69 7a 61 62 6c 65`,所以这里写入`00 17 73 65 72 69 61 6c 2e 54 65 73 74 53 65 72 69 61 6c 69 7a 61 62 6c 65`)
out.writeLong(getSerialVersionUID()); //写入类的序列号 (例子分析:写入-6211228684695072792L的十六进制`a9 cd 48 6b 76 08 8f e8`,可以用Long.toHexString(l)本地输出下Long的十六进制)

byte flags = 0;
if (externalizable) {
// externalizable子类,获取SC_EXTERNALIZABLE 0x04
flags |= ObjectStreamConstants.SC_EXTERNALIZABLE;
int protocol = out.getProtocolVersion();
if (protocol != ObjectStreamConstants.PROTOCOL_VERSION_1) {
flags |= ObjectStreamConstants.SC_BLOCK_DATA;
}
} else if (serializable) {
// serializable子类,获取SC_SERIALIZABLE 0x02
flags |= ObjectStreamConstants.SC_SERIALIZABLE;
}
if (hasWriteObjectData) {
// 类自定义了writeObject(),获取SC_WRITE_METHOD 0x01
flags |= ObjectStreamConstants.SC_WRITE_METHOD;
}
if (isEnum) {
// 该类是枚举类型,获取SC_ENUM 0x10
flags |= ObjectStreamConstants.SC_ENUM;
}
// 写入类的flags (例子分析:写入SC_SERIALIZABLE 0x02)
out.writeByte(flags);
// 写入对象属性的个数 (例子分析:本例除了serialVersionUID有5个属性,写入0x0005,serialVersionUID是static类型不被计入,并且已经在前面写入了)
out.writeShort(fields.length);
// 循环序列化每个属性的信息
for (int i = 0; i < fields.length; i++) {
// 创建一个类的可序列化属性字段描述的实例。ObjectStreamFields的数组用于声明一个类的可序列化属性。
ObjectStreamField f = fields[i];
// 写入第i个属性的类型,具体参考下一部分“补充:ObjectStreamField”的分析
out.writeByte(f.getTypeCode());// (例子分析:有五个属性,3个是原生类型,2个是非原生,按照对应关系本例是intField写入I,boolField写入Z,shortField写入S,stringField写入L,fileField写入L)
// 写入类型的长度+类名称
out.writeUTF(f.getName()); //(例子分析:写入每个属性的长度和名称)
// 如果属性类型不是Java原生类型,调用writeTypeString写入表示对象或者类的类型字符串
if (!f.isPrimitive()) {
// f.getTypeString()=`Ljava/io/File;`(具体参考下一部分“补充:ObjectStreamField”的分析)
// writeTypeString会调用writeString,写入0x74+长度+Ljava/io/File;
out.writeTypeString(f.getTypeString());//(例子分析:写入两个非原生属性的类型`Ljava/io/File;`、`a/lang/String;`)
}
}
}

根据上面代码可以看出,writeNonProxy主要作用是写入类信息和属性信息,具体会写入类长度+类名称+SerialVersionUID+类本身序列化的接口类型如SC_SERIALIZABLE+属性个数+每个属性信息

(例子分析:不算属性信息截至目前写入ac ed 00 05 73 72 00 17 73 65 72 69 61 6c 2e 54 65 73 74 53 65 72 69 61 6c 69 7a 61 62 6c 65 a9 cd 48 6b 76 08 8f e8 02 00 05。接下来分析循环属性完成写属性的信息,循环写入5个属性信息,分别是intField:I+长度0x0008+intField 即 49 00 08 69 6e 74 46 69 65 6c 64,boolField:Z+属性长度0x0009+boolField 即5a 00 09 62 6f 6f 6c 46 69 65 6c 64,shortField:S+长度0x000a+shortField 即 53 00 0a 73 68 6f 72 74 46 69 65 6c 64,fileField:L+属性名长度0x0009+fileField+TC_STRING + 类型长度0x0012+Ljava/io/File; 即 4c 00 09 66 69 6c 65 46 69 65 6c 64 74 00 0e 4c 6a 61 76 61 2f 69 6f 2f 46 69 6c 65 3b,stringField:L+长度0x000b+stringField+TC_STRING + 类型长度0x0012+Ljava/lang/String; 即 4c 00 0b 73 74 72 69 6e 67 46 69 65 6c 64 74 00 12 4c 6a 61 76 61 2f 6c 61 6e 67 2f 53 74 72 69 6e 67 3b。因为原生类型属性信息和非原生类型属性信息位置是不一定的,所以属性信息在流的拼接后的具体信息不能确定。,最后不能忘了加上数据块结束标识TC_ENDBLOCKDATA 0x78)

补充:ObjectStreamField

在上面writeNonProxy序列化类属性信息时,创建了一个用于类的可序列化属性字段描述的ObjectStreamField实例,我们先大体分析下它。

官方文档是这样描述这个类的:

类的可序列化属性的描述。ObjectStreamFields的数组用于声明一个类的可序列化字段。

除了内部类外它具有以下public方法,通过以下方法可以获取某个属性的名称、类型等。

1
2
3
4
5
6
7
8
9
compareTo(Object obj):将此字段与另一个 ObjectStreamField进行比较。
getName():获取此字段的名称。
getOffset():实例数据内的字段偏移量。
getType():获取字段的类型。
getTypeCode():返回字段类型的字符编码。
getTypeString():返回JVM类型签名。
isPrimitive():如果此字段具有原始类型,则返回true
isUnshared():返回指示此ObjectStreamField实例表示的可序列化字段是否未共享的布尔值。
setOffset(int offset):实例数据偏移。

除了上面的方法,还有一些需要特别关注的方法如下:

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
// 在ObjectStreamField的构造函数中,将signature赋值属性的类型码,signature是字段类型的规范JVM签名。另外在getTypeCode的注释中也说明了类型码的对应关系。
/** 类型码对应关系
* Z -> boolean
* B -> byte
* C -> char
* S -> short
* I -> int
* J -> long
* F -> float
* D -> double
* L -> class or interface
* [ -> array
*/
ObjectStreamField(Field field, boolean unshared, boolean showType) {
this.field = field;
this.unshared = unshared;
name = field.getName();
Class<?> ftype = field.getType();
type = (showType || ftype.isPrimitive()) ? ftype : Object.class;
// 调用getClassSignature返回属性的类型码
signature = getClassSignature(ftype).intern();
}

// 获取属性的类型码
private static String getClassSignature(Class<?> cl) {
StringBuilder sbuf = new StringBuilder();
while (cl.isArray()) {
sbuf.append('[');
cl = cl.getComponentType();
}
if (cl.isPrimitive()) {
if (cl == Integer.TYPE) {
sbuf.append('I');
} else if (cl == Byte.TYPE) {
sbuf.append('B');
} else if (cl == Long.TYPE) {
sbuf.append('J');
} else if (cl == Float.TYPE) {
sbuf.append('F');
} else if (cl == Double.TYPE) {
sbuf.append('D');
} else if (cl == Short.TYPE) {
sbuf.append('S');
} else if (cl == Character.TYPE) {
sbuf.append('C');
} else if (cl == Boolean.TYPE) {
sbuf.append('Z');
} else if (cl == Void.TYPE) {
sbuf.append('V');
} else {
throw new InternalError();
}
} else {
// 特别注意 这里L返回的是L+类名(将.替换为/)+“;”,例如java.io对应是“Ljava/io;”
sbuf.append('L' + cl.getName().replace('.', '/') + ';');
}
return sbuf.toString();
}

// 主要处理非原生类型,非原生类型返回类型字符串为JVM type signature
public String getTypeString() {
return isPrimitive() ? null : signature;
}

截止到这里,类元信息已经序列化完成,接下来序列化类实例数据信息,即属性具体值的序列化。

4.3、类实例数据信息序列化writeExternalData/ writeSerialData

writeSerialData&writeExternalData:
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
67
68
69
70
71
// 为给定对象的每个可序列化类写入实例数据,从父类到子类。
private void writeSerialData(Object obj, ObjectStreamClass desc) throws IOException
{
// 首先获取需要序列化的类(desc.getClassDataLayout())
ObjectStreamClass.ClassDataSlot[] slots = desc.getClassDataLayout();
for (int i = 0; i < slots.length; i++) {
ObjectStreamClass slotDesc = slots[i].desc;
//自定义writeObject方法
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 {
// 未自定义writeObject 默认调用属性数据序列化方法defaultWriteFields
defaultWriteFields(obj, slotDesc);
}
}
}

//实现了Externalizable的类实例用writeExternalData来序列化
private void writeExternalData(Externalizable obj) throws IOException {
PutFieldImpl oldPut = curPut;
curPut = null;

if (extendedDebugInfo) {
debugInfoStack.push("writeExternal data");
}
SerialCallbackContext oldContext = curContext;
try {
curContext = null;
// 调用重写的writeExternal()方法
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;
}
属性值信息序列化的关键-默认属性值序列化方法defaultWriteFields:

defaultWriteFields读取目标类中的属性值域,属性值是原生类型直接序列化到底层流,而非原生类型则需要递归调用writeObject0 来序列化。

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
private void defaultWriteFields(Object obj, ObjectStreamClass desc)
throws IOException
{
Class<?> cl = desc.forClass();
if (cl != null && obj != null && !cl.isInstance(obj)) {
throw new ClassCastException();
}

desc.checkDefaultSerialize();

/*** 1、 Java 原生类型属性值序列化 **/
int primDataSize = desc.getPrimDataSize(); // 获取原生类型属性值的个数
if (primVals == null || primVals.length < primDataSize) {
primVals = new byte[primDataSize];
}
// 获取类中的基本数据类型的数据并保存在primVals字节数组中
desc.getPrimFieldValues(obj, primVals);
// primVals的基本类型数据写到底层字节容器
bout.write(primVals, 0, primDataSize, false);

/*** 2、 Java对象类型序列化,递归调用writeObject0 方法 **/
// 获取对应类的所有字段对象
ObjectStreamField[] fields = desc.getFields(false);
Object[] objVals = new Object[desc.getNumObjFields()];
int numPrimFields = fields.length - objVals.length;
// 获取所有对象的属性值
desc.getObjFieldValues(obj, objVals);
//对所有Object类型的字段循环
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();
}
}
}
}

针对单独分析下

1
2
desc.getPrimFieldValues(obj, primVals); // 循环所有原生属性值的序列化
bout.write(primVals, 0, primDataSize, false); // 原生类型序列化
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
void getPrimFieldValues(Object obj, byte[] buf) {
fieldRefl.getPrimFieldValues(obj, buf);
}
// ObjectStreamClass#getPrimFieldValues
// 循环所有原生属性值的序列化
void getPrimFieldValues(Object obj, byte[] buf) {
if (obj == null) {
throw new NullPointerException();
}
// 循环基元数据的类型的序列化:根据类型循环写入属性值
for (int i = 0; i < numPrimFields; i++) {
long key = readKeys[i];
int off = offsets[i];
switch (typeCodes[i]) { //(例子分析:本例typeCodes[i]是{Z、I、S、L、L},需要跟前面属性名顺序对应,比如boolField在第一个,其值在该块也是第一个)
case 'Z':
//(例子分析:写入boolField值Fales,0x00
Bits.putBoolean(buf, off, unsafe.getBoolean(obj, key));
break;
case 'B':
buf[off] = unsafe.getByte(obj, key);
break;
case 'C':
Bits.putChar(buf, off, unsafe.getChar(obj, key));
break;
case 'S':
//(例子分析:写入shortField值12,0x000c
Bits.putShort(buf, off, unsafe.getShort(obj, key));
break;
case 'I':
//(例子分析:写入intField值112,0x00000070
Bits.putInt(buf, off, unsafe.getInt(obj, key));
break;
case 'F':
Bits.putFloat(buf, off, unsafe.getFloat(obj, key));
break;
case 'J':
Bits.putLong(buf, off, unsafe.getLong(obj, key));
break;
case 'D':
Bits.putDouble(buf, off, unsafe.getDouble(obj, key));
break;
default:
throw new InternalError();
}
}
}

(例子分析:第三部分属性值域序列化,按照之前每个属性信息序列化的顺序来写入其对应值,非原生属性值本例是00 00 00 00 70 00 0c,写入非原生类型的属性值,这里不再说明,自行调试)

六、总结-对象流输出规则

image-20210701160611733

1、首先在序列化对象时,把序列化对象的信息分为三部分来存储,如上图:

  • 第一部分是序列化头信息:用于描述序列化协议的信息和版本;

  • 第二部分是类元信息的序列化:用于描述序列化类的名称,属性和父类的名称、属性;

  • 第三部分是属性域的值的描述:用于记录当前类中属性的值。

2、第二部分描述的规则如下:
(1)首先声明标记的是TC_OBJECT = 0x73;
(2)接下来声明一个新类的描述TC_CLASSDESC = 0x72,写入目标类名长度、类名、序列化ID、标记该类使用的序列化方式;
(3)然后标记类属性域的个数、属性类型、属性名称长度、属性名称,如果是非原生类型还有写入类的非原生类型长度和名称
(4)添加标记对象数据块结束标识TC_ENDBLOCKDATA = 0x78
(5)然后开始父类的描述,标记的方式和前面一致,当再没有新的父类时,添加标记TC_NULL = 0x70
另外再提下,目标类本身的类名描述是以”java.io”形式 而如果是属性的非原生类型类名是以”Ljava/io;”标注的,一个是”.”一个是”/“。

3、第三部分的描述规则:
按照从父类到子类的顺序填充属性域的实际值,而对象的属性按照从基本数据类型到对象类型的顺序填充实际值。

4、在第二部分和第三部分都采用了递归的方式进行填充。

另外,Java序列化永远只会对类属性序列化,因为我们只要知道了类的全称,那么方法在内部都一样的,只管去调用就好了;在ObjectOutputStream中专门有一个内部类去处理原生类型的写入,所有的非原生类最终都是由原生类型组成的,非原生类归根结底还是原生类型,所以最终还是落到原生类型的序列化上面

更新-defaultWriteObject

我们在一些类重写writeObject时经常看到会调用defaultWriteObject方法,这里补充下。

方法很简单,在块模式中调用defaultWriteFields写入目标对象的属性域信息。

1
2
3
4
5
6
7
8
9
10
11
public void defaultWriteObject() throws IOException {
SerialCallbackContext ctx = curContext;
if (ctx == null) {
throw new NotActiveException("not in call to writeObject");
}
Object curObj = ctx.getObj();
ObjectStreamClass curDesc = ctx.getDesc();
bout.setBlockDataMode(false);
defaultWriteFields(curObj, curDesc);
bout.setBlockDataMode(true);
}

defaultWriteFields具体如下:

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
private void defaultWriteFields(Object obj, ObjectStreamClass desc)
throws IOException
{
Class<?> cl = desc.forClass(); //类描述信息
if (cl != null && obj != null && !cl.isInstance(obj)) {
throw new ClassCastException();
}

desc.checkDefaultSerialize();//检查是否支持序列化

int primDataSize = desc.getPrimDataSize(); //获取类中可序列化的属性的个数
if (primVals == null || primVals.length < primDataSize) {
primVals = new byte[primDataSize]; //写入属性个数
}
desc.getPrimFieldValues(obj, primVals);
bout.write(primVals, 0, primDataSize, false);

ObjectStreamField[] fields = desc.getFields(false);
Object[] objVals = new Object[desc.getNumObjFields()];
int numPrimFields = fields.length - objVals.length;
desc.getObjFieldValues(obj, objVals);
// 循环写入所有属性信息
for (int i = 0; i < objVals.length; i++) {
if (extendedDebugInfo) {
debugInfoStack.push(
"field (class \"" + desc.getName() + "\", name: \"" +
fields[numPrimFields + i].getName() + "\", type: \"" +
fields[numPrimFields + i].getType() + "\")");
}
try {
// 调用writeObject0写入每个属性对象
writeObject0(objVals[i],fields[numPrimFields + i].isUnshared());
} finally {
if (extendedDebugInfo) {
debugInfoStack.pop();
}
}
}
}

参考:

Java API 8
https://www.cnblogs.com/yanggb/p/10664155.html
https://blog.csdn.net/jenny_995368/article/details/99672570
https://developer.aliyun.com/article/636145
https://blog.csdn.net/weixin_33843947/article/details/89618157