序列化在高性能網絡編程、分布式系統(tǒng)開發(fā)中是舉足輕重的之前有用過Java序列化、ProtocolBuffer等,在這篇文章這里中簡單分析序列化后的byte數組觀察各種序列化的差異與性能,這里主要分析Java序列化、Kryo、ProtocolBuffer序列化;
說明:使用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"); }
}
對比結果發(fā)現Java序列化的性能相比其他兩種慢了好多,程序跑多次取中間值,java序列化花的時間為kryo的6倍、為ProtoclBuffer的30多倍,kryo相差也是為6倍左右。
程序跑完的結果:
jdk serialize:1259msbyte length : 68ac 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:259msbyte length : 4201 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:44msbyte length : 208 08分析byte數組
三種序列化中Java序列化的字節(jié)數組是最長的,kryo稍微比Java序列化短一點而ProtocolBuffer的字節(jié)數組只有兩個字節(jié),下面具體分析每個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 0aJava序列化后字節(jié)數組三部分組成:開頭、類描述、字段值,byte數組中包含了類的版本、元數據、字段描述,字段值等;
開頭,所有的對象序列化后都會有,開頭與類的描述標識都有在ObjectStreamConstants類中定義有常量;ac ed : 為幻數00 05 : 流的版本類描述
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.TestData4f 17 51 92 bb 30 95 54 : serialVersionUID的值02 : 表示對象支持序列化00 01 : 表示字段的個數49 : ASCII碼為I,表示字段為int類型00 02 : 字段名稱長度73 6e : 字段名稱:sn78 : 對象塊結束標志,TC ENDBLOCKDATA70 : 沒有父類 TC NULL
00 00 00 0a : 字段sn的值
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 14Kryo序列化要比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.TestData01 : 一個字段14 : 字段的值,反序列化通過 14 >>> 1 ^ -(14 & 1)可以得到值
ProtocolBuffer序列化的byte數組08 08 ProtoBuf序列化后只有兩個字節(jié),跟前面兩種簡直沒法比,但ProtoBuf和其他兩種的序列化區(qū)別很大,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 兩個字節(jié)。 08 : 為key,使用(1<<3)|0計算得到 08 : 為字段sn經過Varint編碼后的值
總結Java序列化相比Kryo與ProtoBuf序列化后的數組要打得多,速度也慢很多,Java序列化時沒有對byte經過任何處理,而且序列化類的元素也太多有:開頭、類描述、字段值,類的版本、元數據、字段描述,字段值等這些組成,這也是他byte數組大,速度慢的主要原因; Kryo序列化后只有類名和字段信息,相比Java序列化就要簡單了不少,而且Kryo對int、long使用了變長存儲也節(jié)省了不少空間; ProtoBuf序列化后的byte只有key-value對組成還使用了Varint、zigzag編碼,速度極快,而且占用的空間也極少,但是由于ProtoBuf要編寫數據定義文件還要使用ProtoBuf編譯器生成目標語言對象,所以相對Java序列化與Kryo來說會麻煩一點; 用哪種序列化組件主要要是主要取決于需求,如果對跨語言、性能要求比較高、新舊版本兼容要求那這三種中ProtoBuf是不二的選擇,如果不要求跨語言對性能又有一定要求那Kryo是不錯的選擇,如果不跨語言對性能、空間也沒有要求那可以選擇Java序列化; 文章首發(fā)地址:Solinx http://www.solinx.co/archives/377
新聞熱點
疑難解答