最近想把java里执行数据和树全部记录下来,进行回放. 需要动态地通过反射将对象序列化和反序列化. 遇到 execute(List<xxxParam> params) ;
可能就无法通过反射 依赖json反序列化回来,只能将具有自描述的java序列化回来.
rcp框架的接口List<>参数, 反序列化怎么搞的? 用的是java序列化,所以需要继承 Serializable接口
以前保存过一篇文章.
Java序列化与ProtocalBuffer序列化之深入分析(转)
(2013-07-31 20:26:21)转载▼
标签:序列化javaprotocalbufferit |
分类:Tech |
今天看了《Java序列化与ProtocalBuffer序列化之深入分析》,感觉有所收获。原文中对ObjectStreamField中关于属性类型与字符表示的映射没有指出来,在原帖中回复了作者,这里稍作修改并转发。
从一个简单对象的序列化内容来看java序列化与ProtocalBuffer序列化机制的不同之处以及优劣所在。对象准备如下:
父类BaseUserDO.java(getter和setter方法省去)
package public } |
子类UserDO.java继承上面的父类
package public } |
New一个准备序列化的对象
UserDO user= user.setPid(10); user.setId(300); user.setName("JavaSerialize ");//PbSerialize |
Java序列化生成的16进制字节码共151个,内容如下:
AC ED
ProtocalBuffer序列化生成的16进制字节码只有18个,内容如下:
08
151比18,
Java序列化一个对象产生的字节码是自描述型的,也就是说不借助其他的信息,仅仅从它本身的内容就能够找出这个对象的所有信息,比如说类元数据描述、类的属性、属性的值以及父类的所有信息。
Java序列化是将这些信息分成3个部分:
1.开头部分:(颜色表示的都是常量,在java.io.ObjectStreamConstants
AC ED:写入流的幻数,STREAM_MAGIC;
00 05:写入流的版本号,STREAM_VERSION;
2.类描述部分:(包括父类的描述信息)
73:TC_OBJECT,
72:TC_CLASSDESC,声明这里开始一个新Class;
00 18:class类名的长度(也就是”serialize.compare.UserDO”的长度);
73 65 72 69 61 6C 69 7A 65 2E 63 6F6D 70 61 72 65 2E 55 73 65 72 44 4F:这24个字节码转化成字符串就是:”serialize.compare.UserDO”;
5A A9 D2 C7 76 44 DDE3:
(如果没有serialVersionUID会随机生成一个);
02:标记号,该值表示该对象支持序列化;
00 02:该类所包含属性的个数(id、name);
49:字符“I”的值,代表属性的类型为Integer类型(参见ObjectStreamField);
00 02:属性名称的长度;(“id”.length()==2);
69 64:属性名称:id;
4C:
00 04:属性名称的长度;(“name”.length()==4);
6E 61 6D 65:属性名称:”name”;
74:TC_STRING,代表一个new String,这里是用来引用父类BaseUserDO;
00 12:对象签名的长度;
4C 6A 61 76 61 2F 6C 61 6E 67 2F 5374 72 69 6E 67 3B:
78:TC_ENDBLOCKDATA对象块结束的标志,74和78之间的内容是用来说明UserDO和
72:TC_CLASSDESC,声明这里开始一个新Class;即父类BaseUserDO;
00 1C:class类名的长度(也就是”serialize.compare.BaseUserDO”的长度);
73 65 72 69 61 6C 69 7A 65 2E 63 6F6D 70 61 72 65 2E 42 61 73 65 55 73 65 72 44 4F:”serialize.compare.BaseUserDO”;
4F 17 51 92 BB 30 9554:
02:
00 01:
49:
00 03:属性名称的长度;
70 69 64:”pid”;
78: TC_ENDBLOCKDATA对象块结束的标志;
70:TC_NULL,说明没有其他超类的标志;
3.属性值部分:(从父类开始将实例对象的实际值输出)
从上面的解析可以看出序列化的内容大部分到在描述自己,而我们关心的值的部分即红颜色的部分只暂很小的一个部分,21个字节,占比13.91%。空间浪费严重。
再来看下ProtocalBuffer序列化,Pb在序列化之前需要定义一个.proto文件,用于描述一个message的数据结构,如下:
package tutorial; option java_package ="serialize.compare"; option java_outer_classname ="UserAgent"; message UserDO{ } |
再使用PB的编译器编译这个.proto文件生成一个代理类,即UserAgent.java,对象的序列化和反序列化就通过这个代理类来实现;对象经过序列化后会成为一个二进制数据流,该流中的数据为一系列的
Key:是由公式计算出来的:(field_number <<3) | wire_type,即Key的后三个比特为wire_type;
Value:是进过编码处理过的字节码;包括:Varint、zigzag等;
wire_type对应表:
Type |
Meaning |
Used For |
0 |
Varint |
int32,int64, uint32, uint64, sint32, sint64, bool, enum |
1 |
64-bit |
fixed64,sfixed64, double |
2 |
Length-delimited |
string,bytes, embedded messages, packed repeated fields |
3 |
Start group |
Groups (deprecated) |
4 |
End group |
Groups (deprecated) |
5 |
32-bit |
fixed32,sfixed32, float |
Varint
了解了以上知识后就可以开解析一下以下序列化内容了:
08
08 0A:这是一个key-value对,08是key,由(1<<3)|0计算得出;
0A是value,因为采用Varint编码10只需要一个字节来表示;java序列化中则是用4个字节来表示:00 00 00 0A
10 AC 02: key由(2<<3)|0计算得出;value也是采用Varint编码;
演算一下:AC > 10*16+12 > 172 > 128+32+8+4 > 1010 1100
(高位是1说明还没有结束,下一个字节也是这个值的一部分)
02 > 0000 0010 (高位是0说明结束)
> 1010 11000000 0010
> 010 1100 000 0010(去掉高位,因为高位只是个标记位)
> 000 0010 010 1100(little-endian互换位置)
> 100101100 (二进制)
> 300 (十进制)
1A 0B 50 62 53 65 72 69 61 6C 69 7A 65:
1A是key:(3<<3)|2 (string的wire_type=2参照上面的表格)
0B:表示这个string的长度为11
50 62 53 65 72 69 61 6C 69 7A 65:string的内容:”PbSerialize”;
可以看出这些字节码中没有任何类元素的描述,属性也是用tag来表示,
而且int32、int64等number型都采用了Varint编码,
我们的业务对象很多属性都是用int型的用来表示各种状态,而且值都是0,1,2,3之类的少于128的值,
那么这些value都只需用一个字节来存储,大大减少了空间。Value 占比也非常高:达到了77.78%。