序列化在高性能網絡編程、分佈式系統開發中是舉足輕重的以前有用過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