序列化在高性能网络编程、分布式系统开发中是举足轻重的以前有用过Java序列化、ProtocolBuffer等,在这篇文章这里中简单分析序列化后的byte数组观察各类序列化的差别与性能,这里主要分析Java序列化、Kryo、ProtocolBuffer序列化;java
说明:使用Java序列化、Kryo、ProtocolBuffer分别序列化、反序列化十万次比较三种方式分别使用的时间;编程
入口程序:
数组
}网络
public class TestData implements Serializable { int sn; public void setSn(int sn) { this.sn = sn; } } public class SerializeCompare { public static void main(String[] args){ TestData testData=new TestData(); testData.setSn(10); SerializeCompare serialize = new SerializeCompare(); try { serialize.jdkSerialize(testData); System.out.println("---------------------------------------------------------------"); serialize.kryoTest(testData); System.out.println("---------------------------------------------------------------"); serialize.protocolTest(); } catch (ClassNotFoundException e) { e.printStackTrace(); } catch (IOException e) { e.printStackTrace(); } } public void jdkSerialize(TestData testData) throws IOException, ClassNotFoundException { JdkSerialize jdkSerialize = new JdkSerialize(); byte[] jdkByte = null; TestData deSerialize = null; long startTime = System.currentTimeMillis(); for (int i = 0; i < 100000; i++) { jdkByte = jdkSerialize.serialize(testData); deSerialize = (TestData) jdkSerialize.deSerialize(jdkByte); } long endTime = System.currentTimeMillis(); System.out.println("jdk serialize:" + (endTime - startTime) + "ms"); } public void kryoTest(TestData testData) { KryoSerialize kryoSerialize = new KryoSerialize(); byte[] kryoByte = null; TestData kryObj = null; long startTime = System.currentTimeMillis(); for (int i = 0; i < 100000; i++) { kryoByte = kryoSerialize.serialize(testData); kryObj = (TestData) kryoSerialize.deSerialize(kryoByte); } long endTime = System.currentTimeMillis(); System.out.println("kryo serialize:" + (endTime - startTime) + "ms"); } public void protocolTest(){ TestDataProto.TestData.Builder testData=TestDataProto.TestData.newBuilder(); testData.setSn(8); byte[] datas = null; TestDataProto.TestData temp = null; long startTime = System.currentTimeMillis(); for (int i = 0; i < 100000; i++) { datas = testData.build().toByteArray(); try { temp =TestDataProto.TestData.parseFrom(datas); } catch (InvalidProtocolBufferException e) { e.printStackTrace(); } } long endTime = System.currentTimeMillis(); System.out.println("protocol serialize:" + (endTime - startTime) + "ms"); }
对比结果发现Java序列化的性能相比其余两种慢了好多,程序跑屡次取中间值,java序列化花的时间为kryo的6倍、为ProtoclBuffer的30多倍,kryo相差也是为6倍左右。框架
程序跑完的结果:分布式
jdk serialize:1259ms byte length : 68 ac ed 00 05 73 72 00 26 63 6f 2e 73 6f 6c 69 6e 78 2e 64 65 6d 6f 2e 73 65 72 69 61 6c 69 7a 65 2e 64 61 74 61 2e 54 65 73 74 44 61 74 61 4f 17 51 92 bb 30 95 54 02 00 01 49 00 02 73 6e 78 70 00 00 00 0a --------------------------------------------------------------- kryo serialize:259ms byte length : 42 01 00 63 6f 2e 73 6f 6c 69 6e 78 2e 64 65 6d 6f 2e 73 65 72 69 61 6c 69 7a 65 2e 64 61 74 61 2e 54 65 73 74 44 61 74 e1 01 14 --------------------------------------------------------------- protocol serialize:44ms byte length : 2 08 08
三种序列化中Java序列化的字节数组是最长的,kryo稍微比Java序列化短一点而ProtocolBuffer的字节数组只有两个字节,下面具体分析每一个byte数组;
Java序列化的byte数组
ac ed 00 05 73 72 00 26 63 6f 2e 73 6f 6c 69 6e 78 2e 64 65 6d 6f 2e 73 65 72 69 61 6c 69 7a 65 2e 64 61 74 61 2e 54 65 73 74 44 61 74 61 4f 17 51 92 bb 30 95 54 02 00 01 49 00 02 73 6e 78 70 00 00 00 0a
Java序列化后字节数组三部分组成:开头、类描述、字段值,byte数组中包含了类的版本、元数据、字段描述,字段值等;性能
开头,全部的对象序列化后都会有,开头与类的描述标识都有在ObjectStreamConstants类中定义有常量;
ac ed : 为幻数
00 05 : 流的版本
类描述ui
73 : TC_OBJECT 新对象
72 : 新类描述
00 26 : 类名长度
63 6f 2e 73 6f 6c 69 6e 78 2e 64 65 6d 6f 2e 73 65 72 69 61 6c 69 7a 65 2e 64 61 74 61 2e 54 65 73 74 44 61 74 61 :类名 co.solinx.demo.serialize.data.TestData
4f 17 51 92 bb 30 95 54 : serialVersionUID的值
02 : 表示对象支持序列化
00 01 : 表示字段的个数
49 : ASCII码为I,表示字段为int类型
00 02 : 字段名称长度
73 6e : 字段名称:sn
78 : 对象块结束标志,TC ENDBLOCKDATA
70 : 没有父类 TC NULLthis
00 00 00 0a : 字段sn的值google
kryo序列化的byte数组
01 00 63 6f 2e 73 6f 6c 69 6e 78 2e 64 65 6d 6f 2e 73 65 72 69 61 6c 69 7a 65 2e 64 61 74 61 2e 54 65 73 74 44 61 74 e1 01 14
Kryo序列化要比Java序列化简单不少,只有标识+类名+字段三部分组成,对String使用最后一位byte+X70标识String结束;
01 00 : 标识
63 6f 2e 73 6f 6c 69 6e 78 2e 64 65 6d 6f 2e 73 65 72 69 61 6c 69 7a 65 2e 64 61 74 61 2e 54 65 73 74 44 61 74 e1 : 类的名称类名 co.solinx.demo.serialize.data.TestData
01 : 一个字段
14 : 字段的值,反序列化经过 14 >>> 1 ^ -(14 & 1)能够获得值
ProtocolBuffer序列化的byte数组
08 08
ProtoBuf序列化后只有两个字节,跟前面两种简直无法比,但ProtoBuf和其余两种的序列化区别很大,ProtoBuf为与语言平台无关的,须要编写自定义的proto文件定义数据格式,而后用ProtoBuf编译器编译成目标语言:C++、Java、Python等的代理类,对象的序列化、反序列化都须要使用该类来完成;
ProtoBuf序列化对象的byte数组由一系列的key-Value组成,key计算公式为:(field_number<<3)|wire_type、Value:通过编码后的byte,ProtoBuf使用了Varint、zigzag编码极大的压缩了序列化后byte数组的大小,因此当前咱们看到的byte数组只有08 08 两个字节。
08 : 为key,使用(1<<3)|0计算获得
08 : 为字段sn通过Varint编码后的值
Java序列化相比Kryo与ProtoBuf序列化后的数组要打得多,速度也慢不少,Java序列化时没有对byte通过任何处理,并且序列化类的元素也太多有:开头、类描述、字段值,类的版本、元数据、字段描述,字段值等这些组成,这也是他byte数组大,速度慢的主要缘由;
Kryo序列化后只有类名和字段信息,相比Java序列化就要简单了很多,并且Kryo对int、long使用了变长存储也节省了很多空间;
ProtoBuf序列化后的byte只有key-value对组成还使用了Varint、zigzag编码,速度极快,并且占用的空间也极少,可是因为ProtoBuf要编写数据定义文件还要使用ProtoBuf编译器生成目标语言对象,因此相对Java序列化与Kryo来讲会麻烦一点;
用哪一种序列化组件主要要是主要取决于需求,若是对跨语言、性能要求比较高、新旧版本兼容要求那这三种中ProtoBuf是不二的选择,若是不要求跨语言对性能又有必定要求那Kryo是不错的选择,若是不跨语言对性能、空间也没有要求那能够选择Java序列化;
文章首发地址:Solinx
http://www.solinx.co/archives/377