Java序列化的基础知识 请参考之前的文章 ‘Java基础 之 序列化与反序列化’
序列化数据的存储结构:
Java序列化后存储的信息包括:类元数据描述、类的属性、父类信息以及属性域的值。
编写一个测试类:
1 | public class SerializableTest implements Serializable { |
通过Junit进行序列化,生成序列化后的对象:
1 |
|
使用strings打开ObjectSaver.obj 文件,可以看到存储下来的可打印的字符信息如下:
1 | java.util.Datehj |
二进制内容为:
1 | aced 0005 7400 09e4 b8a5 e698 8ee6 988e |
第一部分:
aced STREAM_MAGIC 流的幻数,用于标识序列化协议;
0005 STREAM_VERSION 标识序列化协议的版本号;
一些标识性字符可以参考类:ObjectStreamConstants
ObjectOutputStream.writeStreamHeader()方法:
1 | protected void writeStreamHeader() throws IOException { |
第二部分:
1 | objectOutputStream.writeObject(“严明明”) |
- 74 标识TC_STRING
- 00 09 标识第一个String的长度为9个字节
- e4 b8a5 e698 8ee6 988e 表示String类型的值:严明明
1 | objectOutputStream.writeObject(new Date()) |
- 7372 标识TC_OBJECT 和 TC_CLASSDESC
- 000e 标识类名称长度为14个字节
- 6a61 7661 2e75 7469 6c2e 4461 7465 表示类型值:java.util.Date
- 686a 8101 4b59 7419 标识uid对象序列化ID的类型为long型,占用8个字节
- 03 这一个字节可能有十种值标识:参考java.io.ObjectStreamConstants#SC_*
- 0000 标识类属性个数,因为是Date类型,所以没有自定义属性
- 78 标识域类型TC_ENDBLOCKDATA ,因为属性个数为0,所以对象数据结束
- 70 再没有父类的标识
- 77 对象数据块开始,TC_BLOCKDATA
- 08 标识数据长度为8个字节
- 00 0001 5356 b659 b7 标识new Date()的对象时间戳long型,占用8个字节
- 78 TC_ENDBLOCKDATA 对象数据库结束标识
1 | objectOutputStream.writeObject(new ObjectSaver(“测试类”,30)) |
- 7372 0037 表示TC_OBJECT 和 TC_CLASSDESC ,且类名称长度为55个字节
- 636f
6d2e 6a61 7661 2e64 656d 6f2e 7365 7269
616c 697a 6162 6c65 2e53 6572 6961 6c69
7a61 626c 6554 6573 7424 4f62 6a65 6374
5361 7665 72
表示类名值:com.java.demo.serializable.SerializableTest$ObjectSaver - eb bbba f5cb 5ec7 4b 标识ObjectSaver对象序列化ID的类型为long型,占用8个字节 ,值为-1460368089309853877L,因为uid为静态属性,所以属于类元信息一部分
- 02 这一个字节可能有十种值标识,其中02标识SC_SERIALIZABLE,此类继承了Serializable接口
- 0003 标识类属性个数为 3 个,包含this;
- 49 域类型,49转十进制为73,73在ASC码中对应的是 I ,因为old为int类型
- 00 03 标识属性名称长度为3个字节
- 6f 6c64 标识属性名称为 old
- 4c 域类型,4c转十进制为76,76在ASC码中对应的是 L,因为name为String对象
- 00 04 标识属性名称长度为4个字节,name字符占四个字符
- 6e 616d 65 标识属性名称的值 name
- 74 标识TC_STRING一个新的字符串
- 0012 域类型长度为18个字节
- 4c6a 6176 612f 6c61 6e67 2f53 7472 696e
673b
对象类型签名 Ljava/lang/String; 包含封号 - 4c 域类型,4c转十进制为76,76在ASC码中对应的是 L
- 00 06 标识属性名称长度为6个字节
- 74 6869 7324 30 标识字符串 this$0
- 74 标识TC_STRING一个新的字符串
- 002d 域类型长度为45个字节
- 4c63
6f6d 2f6a 6176 612f 6465 6d6f 2f73 6572
6961 6c69 7a61 626c 652f 5365 7269 616c
697a 6162 6c65 5465 7374 3b
Lcom/java/demo/serializable/SerializableTest; - 78 标识域类型TC_ENDBLOCKDATA
上面为之类ObjectSaver的描述信息和元信息,下面为父类SerializableTest的描述信息和元信息
- 72 标识TC_CLASSDESC
- 002b 标识类名称长度为43个字节
- 63
6f6d 2e6a 6176 612e 6465 6d6f 2e73 6572
6961 6c69 7a61 626c 652e 5365 7269 616c
697a 6162 6c65 5465 7374
标识类字符串:com.java.demo.serializable.SerializableTest - 1ae4 7592 b513 4860 标识ObjectSaver对象序列化ID的类型为long型,占用8个字节 ,值为-1937803012639770720L
- 02 这一个字节可能有十种值标识,其中02标识SC_SERIALIZABLE,此类继承了Serializable接口
- 00 01 标识类属性个数为1个
- 49 域类型,49转十进制为73,73在ASC码中对应的是 I ,因为old为int类型
- 0006 标识属性名称长度为6个字节
- 6661 7468 6572 标识father六个字符;
- 7870 TC_ENDBLOCKDATA 对象数据库结束标识,且没有父类
第三部分:
接下来是对象属性域的值部分,按照从父类到子类的顺序写入域的值
- 0000 0001 标识的十进制为1,对应父类father的值为1
- 0000 001e 标识十进制为30,标识之类old的值为30
- 74 标识TC_STRING一个新的字符串
- 0009 标识占用9个字节
- e6b5 8be8 af95 e7b1 bb 标识字符“测试类”
- 73 71 标识TC_OBJECT TC_REFERENCE
- 007e 0006 0000 0000
总结 Java序列化算法的基本步骤
- 输出序列化的头部信息,包括序列化协议的幻数和版本;
- 基本类型按照一字节的类型标识、两字节类型长度、N个字节值
- 基本对象类型,7372标识OBJECT和CLASSDESC,两字节类型长度,8字节uid,一些辅助信息
- 复杂对象类型,第一步按照由子类到父类的顺序,递归的输出类的描述信息,知道不再有父类为止;类描述信息按照类元数据,类属性信息的顺序写入序列化流中;第二步按照由父类到之类的顺序,递归的输出对象域对象域的实际数据值;而对象的属性信息是按照基本类型到java对象类型的顺序写入序列化流中,其中java对象类型的属性会从第一步重新开始递归的输出,知道不再存在java对象类型的属性。