Java 序列化反序列化框架比较
文章目錄
- 一、簡介
- 二、序列化框架
- 1、JDK
- 2、XML序列化
- 3、JSON序列化
- 4、Hessian
- 5、Avro序列化
- 6、Kyro序列化
- 7、Protostuff
 
- 三、序列化框架對比測試
- 1、對象準(zhǔn)備
- 2、JDK方式
- 3、FastJson方式
- 4、Hessian方式
- 5、Protostuff方式
- 6、測試代碼
 
- 四、總結(jié)
- 五、序列化應(yīng)用場景
- 六、注意事項
一、簡介
序列化:將Java對象轉(zhuǎn)化成字節(jié)數(shù)組。
反序列化:將字節(jié)數(shù)組轉(zhuǎn)化成Java對象。
影響序列化選擇有兩個因素:
-  序列化之后碼流的大小,如果太大,那么將會影響網(wǎng)絡(luò)傳輸?shù)男阅堋?/p> 
-  序列化和反序列化過程的性能。 
二、序列化框架
1、JDK
2、XML序列化
XML 協(xié)議,良好的可讀性,自由度極高的擴(kuò)展性,成了很長一段時間的序列化標(biāo)準(zhǔn)規(guī)范;
可以說XML序列化是開發(fā)中最常見也是發(fā)展時間最久的協(xié)議,并且支持跨進(jìn)程和跨語言交互。
但是缺陷也很明顯,即XML規(guī)范下的每一個屬性和值都是固定的標(biāo)簽形式,導(dǎo)致序列化后的字節(jié)流文件很大,而且解析復(fù)雜,效率很低。
方案:最常見的是 XStream 和 Java自帶的XML序列化和反序列化 兩種。
3、JSON序列化
XML序列化發(fā)展了多年后,也浮現(xiàn)了一些問題,比如開發(fā)并不簡便,解析XML復(fù)雜度較高,還有XML的標(biāo)準(zhǔn)規(guī)范比較多,自由度過高,導(dǎo)致很難有效的指定格式校驗等,于是一種新的 輕量級的序列化交互的方案--JSON(JavaScript Object Notation) 出現(xiàn)了,相對于XML來說,json格式語法簡單,自由度較高,有很高的可讀性,并且在JSON序列化后的字節(jié)流小于XML序列化的結(jié)果,解析起來更方便,于是基于JSON的接口成了新的標(biāo)準(zhǔn)規(guī)范之一。
方案:最常見的Jackson、阿里巴巴開源的FastJson、谷歌的GSON。
4、Hessian
簡單說來,Hessian 是一個輕量級的RPC框架。
它基于HTTP協(xié)議傳輸,使用Hessian二進(jìn)制序列化,對于數(shù)據(jù)包比較大的情況比較友好。
5、Avro序列化
Avro 序列化設(shè)計初衷是為了支持大批量數(shù)據(jù)交換的應(yīng)用,支持二進(jìn)制序列化方式,并且自身提供了動態(tài)語言支持,可以更加便捷、快速處理大批量的Avro數(shù)據(jù)
6、Kyro序列化
Kyro序列化 是主流的比較成熟的序列化方案之一,目前廣泛使用在大數(shù)據(jù)組件中,比如Hive、Storm等,性能比起Hessian還要優(yōu)越,但是缺陷較明顯,不支持跨語言交互,在dubbo2.6.x版本開始已經(jīng)加入了Kyro序列化的支持。
7、Protostuff
Protobuf 是谷歌提出的序列化方案,不同的是此方案 獨立于語言、平臺,谷歌提供了多個語言如java、c、go、python等語言的實現(xiàn),也提供了多平臺的庫文件支持,使用比較廣泛,優(yōu)點在于 性能開銷很小,壓縮率很高,但是缺陷也很明顯,可讀性很差,并且protobuf需要使用特定語言的庫進(jìn)行翻譯轉(zhuǎn)換,使用起來較為麻煩。
三、序列化框架對比測試
下面我們挑選 JDK、FastJson、Hessian、Protostuff 四種序列化方式進(jìn)行對比測試。
測試之前我們需要準(zhǔn)備序列化對象,對象在序列化時,需要注意以下幾個關(guān)鍵字:
-  Serializable:接口,是一個標(biāo)志性接口,標(biāo)識可以在 JVM 中進(jìn)行序列化,JVM 會為該類自動生成一個序列化版本號。參與序列化與反序列化的類必須實現(xiàn) Serializable 接口。 
-  serialVersionUID:類屬性,序列化版本號,用于給 JVM 區(qū)別同名類,沒有提供版本號,JVM會默認(rèn)提供序列化版本號。 
-  transient:關(guān)鍵字,當(dāng)序列化時,不希望某些屬性參與,則可以使用這個關(guān)鍵字標(biāo)注該屬性。 
1、對象準(zhǔn)備
SHeader.java
package com.springboottest.serialize.model;import java.io.Serializable; import java.util.Map;public class SHeader implements Serializable {private int code;private int length;private String type;private Map<String, Object> attach;public int getCode() {return code;}public void setCode(int code) {this.code = code;}public int getLength() {return length;}public void setLength(int length) {this.length = length;}public String getType() {return type;}public void setType(String type) {this.type = type;}public Map<String, Object> getAttach() {return attach;}public void setAttach(Map<String, Object> attach) {this.attach = attach;} }SRequest.java
package com.springboottest.serialize.model;import java.io.Serializable; import java.util.HashMap; import java.util.Map;public class SRequest implements Serializable {private String version;private SHeader header;private Object body;public String getVersion() {return version;}public void setVersion(String version) {this.version = version;}public SHeader getHeader() {return header;}public void setHeader(SHeader header) {this.header = header;}public Object getBody() {return body;}public void setBody(Object body) {this.body = body;}public static class SRequestBuilder{public static SRequest build(){SRequest request = new SRequest();request.setVersion("1.0.0");SHeader header = new SHeader();header.setCode(200);header.setLength(202);header.setType("Object");Map<String, Object> attach = new HashMap<String, Object>();attach.put("name", "davis");attach.put("age", 30);header.setAttach(attach);request.setHeader(header);request.setBody("{\"code\":200, \"msg\":\"success\"}");return request;}} }序列化接口 AbstractSerialize.java
package com.springboottest.serialize;public abstract class AbstractSerialize {public abstract <T> byte[] serialize(T obj);public abstract <T> T deserialize(byte[] data, Class<T> clazz); }2、JDK方式
JDK自帶的序列化反序列化方式需要用到的兩個類:
-  ObjectOutputStream:IO類,包含序列化對象的方法,writeObject(); 
-  ObjectInputStream:IO類,包含反序列化對象的方法,readObject(); 
具體代碼 JDKSerialize.java
package com.springboottest.serialize;import com.springboottest.serialize.model.SRequest;import java.io.ByteArrayInputStream; import java.io.ByteArrayOutputStream; import java.io.ObjectInputStream; import java.io.ObjectOutputStream;public class JDKSerialize extends AbstractSerialize{@Overridepublic <T> byte[] serialize(T obj) {if (obj == null){throw new NullPointerException();}ByteArrayOutputStream bos = new ByteArrayOutputStream();try {ObjectOutputStream oos = new ObjectOutputStream(bos);oos.writeObject(obj);return bos.toByteArray();} catch (Exception ex) {ex.printStackTrace();}return new byte[0];}@Overridepublic <T> T deserialize(byte[] data, Class<T> clazz) {ByteArrayInputStream bis = new ByteArrayInputStream(data);try {ObjectInputStream ois = new ObjectInputStream(bis);T obj = (T)ois.readObject();return obj;} catch (Exception ex) {ex.printStackTrace();}return null;} }3、FastJson方式
FastJson 引入方式(pom.xml):
<dependency><groupId>com.alibaba</groupId><artifactId>fastjson</artifactId><version>1.2.56</version> </dependency>具體代碼 FastjsonSerialize.java
package com.springboottest.serialize; import com.alibaba.fastjson.JSON;public class FastjsonSerialize extends AbstractSerialize{@Overridepublic <T> byte[] serialize(T obj) {if (obj == null){throw new NullPointerException();}String json = JSON.toJSONString(obj);byte[] data = json.getBytes();return data;}@Overridepublic <T> T deserialize(byte[] data, Class<T> clazz) {T obj = JSON.parseObject(new String(data),clazz);return obj;} }4、Hessian方式
Hessian 引入方式(pom.xml):
<dependency><groupId>com.caucho</groupId><artifactId>hessian</artifactId><version>4.0.60</version> </dependency>具體代碼 HessianSerialize.java
package com.springboottest.serialize;import com.caucho.hessian.io.HessianInput; import com.caucho.hessian.io.HessianOutput;import java.io.ByteArrayInputStream; import java.io.ByteArrayOutputStream;public class HessianSerialize extends AbstractSerialize{@Overridepublic <T> byte[] serialize(T obj) {if (obj == null){throw new NullPointerException();}try{ByteArrayOutputStream bos = new ByteArrayOutputStream();HessianOutput ho = new HessianOutput(bos);ho.writeObject(obj);return bos.toByteArray();} catch(Exception ex){}return new byte[0];}@Overridepublic <T> T deserialize(byte[] data, Class<T> clazz) {if (data == null){throw new NullPointerException();}try{ByteArrayInputStream bis = new ByteArrayInputStream(data);HessianInput hi = new HessianInput(bis);return (T)hi.readObject();} catch(Exception ex){ex.printStackTrace();}return null;} }5、Protostuff方式
Protostuff 引入方式(pom.xml):
<dependency><groupId>io.protostuff</groupId><artifactId>protostuff-core</artifactId><version>1.6.0</version><scope>compile</scope> </dependency><dependency><groupId>io.protostuff</groupId><artifactId>protostuff-runtime</artifactId><version>1.6.0</version> </dependency>具體代碼 ProtostuffSerialize.java
package com.springboottest.serialize;import io.protostuff.LinkedBuffer; import io.protostuff.ProtostuffIOUtil; import io.protostuff.Schema; import io.protostuff.runtime.RuntimeSchema; import java.util.Map; import java.util.concurrent.ConcurrentHashMap;public class ProtostuffSerialize extends AbstractSerialize{/*** 避免每次序列化都重新申請Buffer空間*/private static LinkedBuffer buffer = LinkedBuffer.allocate(LinkedBuffer.DEFAULT_BUFFER_SIZE);/*** 緩存Schema*/private static Map<Class<?>, Schema<?>> schemaCache = new ConcurrentHashMap<Class<?>, Schema<?>>();@Overridepublic <T> byte[] serialize(T obj) {if (obj == null){throw new NullPointerException();}Class<T> clazz = (Class<T>) obj.getClass();Schema<T> schema = getSchema(clazz);byte[] data;try {data = ProtostuffIOUtil.toByteArray(obj, schema, buffer);} finally {buffer.clear();}return data;}@Overridepublic <T> T deserialize(byte[] data, Class<T> clazz) {Schema<T> schema = getSchema(clazz);T obj = schema.newMessage();ProtostuffIOUtil.mergeFrom(data, obj, schema);return obj;}private static <T> Schema<T> getSchema(Class<T> clazz) {Schema<T> schema = (Schema<T>) schemaCache.get(clazz);if (schema == null) {//這個schema通過RuntimeSchema進(jìn)行懶創(chuàng)建并緩存//所以可以一直調(diào)用RuntimeSchema.getSchema(),這個方法是線程安全的schema = RuntimeSchema.getSchema(clazz);if (schema != null) {schemaCache.put(clazz, schema);}}return schema;} }6、測試代碼
SerializeTest.java
package com.springboottest.serialize;import com.springboottest.serialize.model.SRequest;public class SerializeTest {public static void main(String[] args) {System.out.println("------------ JDK Serialize ------------");test(new JDKSerialize());System.out.println();System.out.println("------------ FastJson Serialize ------------");test(new FastjsonSerialize());System.out.println();System.out.println("------------ Hessian Serialize ------------");test(new HessianSerialize());System.out.println();System.out.println("------------ Protostuff Serialize ------------");test(new ProtostuffSerialize());System.out.println();}public static void test(AbstractSerialize abstractSerialize){for(int i=0;i<5;i++){testSerialize(abstractSerialize);System.out.println("----------------------");}}public static void testSerialize(AbstractSerialize abstractSerialize){AbstractSerialize serialize = abstractSerialize;SRequest request = SRequest.SRequestBuilder.build();SRequest result = null;byte[] bytes = serialize.serialize(request);System.out.println("字節(jié)長度:" + bytes.length);int count = 100;long seriDuration = 0, deseriDuration = 0;long start = 0, end = 0;for(int i=0;i<count;i++){start = System.nanoTime();bytes = serialize.serialize(request);end = System.nanoTime();seriDuration += (end - start);start = System.nanoTime();result = serialize.deserialize(bytes, SRequest.class);end = System.nanoTime();deseriDuration += (end - start);}System.out.println("總次數(shù):" + count);System.out.println("序列化總耗時:" + seriDuration/1000 + "us");System.out.println("反序列化總耗時:" + deseriDuration/1000 + "us");System.out.println("總耗時:" + (seriDuration + deseriDuration)/1000 + "us");} }輸出結(jié)果(序列化和反序列化100次):
------------ JDK Serialize ------------ 字節(jié)長度:526 總次數(shù):100 序列化總耗時:10616us 反序列化總耗時:73580us 總耗時:84197us ---------------------- 字節(jié)長度:526 總次數(shù):100 序列化總耗時:5269us 反序列化總耗時:18589us 總耗時:23859us ---------------------- 字節(jié)長度:526 總次數(shù):100 序列化總耗時:3492us 反序列化總耗時:12892us 總耗時:16385us ---------------------- 字節(jié)長度:526 總次數(shù):100 序列化總耗時:3483us 反序列化總耗時:9978us 總耗時:13462us ---------------------- 字節(jié)長度:526 總次數(shù):100 序列化總耗時:2414us 反序列化總耗時:9121us 總耗時:11535us ---------------------------------- FastJson Serialize ------------ 字節(jié)長度:150 總次數(shù):100 序列化總耗時:7581us 反序列化總耗時:42988us 總耗時:50570us ---------------------- 字節(jié)長度:150 總次數(shù):100 序列化總耗時:8240us 反序列化總耗時:10351us 總耗時:18592us ---------------------- 字節(jié)長度:150 總次數(shù):100 序列化總耗時:7231us 反序列化總耗時:9794us 總耗時:17026us ---------------------- 字節(jié)長度:150 總次數(shù):100 序列化總耗時:5001us 反序列化總耗時:6936us 總耗時:11938us ---------------------- 字節(jié)長度:150 總次數(shù):100 序列化總耗時:3552us 反序列化總耗時:5458us 總耗時:9011us ---------------------------------- Hessian Serialize ------------ 字節(jié)長度:243 總次數(shù):100 序列化總耗時:14385us 反序列化總耗時:30783us 總耗時:45168us ---------------------- 字節(jié)長度:243 總次數(shù):100 序列化總耗時:9114us 反序列化總耗時:13857us 總耗時:22972us ---------------------- 字節(jié)長度:243 總次數(shù):100 序列化總耗時:7527us 反序列化總耗時:12365us 總耗時:19892us ---------------------- 字節(jié)長度:243 總次數(shù):100 序列化總耗時:5211us 反序列化總耗時:9830us 總耗時:15042us ---------------------- 字節(jié)長度:243 總次數(shù):100 序列化總耗時:6858us 反序列化總耗時:10122us 總耗時:16981us ---------------------------------- Protostuff Serialize ------------ 字節(jié)長度:86 總次數(shù):100 序列化總耗時:2404us 反序列化總耗時:5213us 總耗時:7617us ---------------------- 字節(jié)長度:86 總次數(shù):100 序列化總耗時:1450us 反序列化總耗時:1668us 總耗時:3119us ---------------------- 字節(jié)長度:86 總次數(shù):100 序列化總耗時:1183us 反序列化總耗時:1440us 總耗時:2624us ---------------------- 字節(jié)長度:86 總次數(shù):100 序列化總耗時:1038us 反序列化總耗時:1316us 總耗時:2355us ---------------------- 字節(jié)長度:86 總次數(shù):100 序列化總耗時:991us 反序列化總耗時:1509us 總耗時:2501us ----------------------四、總結(jié)
| JDK | 526 | 11 ~ 23 | 65 ~ 78 | 215 ~ 385 | 
| FastJson | 150 | 9 ~ 18 | 11 ~ 46 | 20 ~ 40 | 
| Hessian | 243 | 15 ~ 23 | 45 ~ 63 | 241 ~ 355 | 
| Protostuff | 86 | 2 ~ 7 | 4 ~ 10 | 11 ~ 20 | 
注:
- 字節(jié)流大小單位:B(字節(jié))
- 序列化和反序列化總耗時單位:us(微秒)
從圖表中可以看出:
-  JDK方式的碼流最大,不利于網(wǎng)絡(luò)傳輸; 
-  從整體來看,Prorostuff的碼流最小,序列化性能最好。 
五、序列化應(yīng)用場景
-  序列化會將內(nèi)存中對象的狀態(tài)轉(zhuǎn)換成二進(jìn)制文件保存到磁盤當(dāng)中,當(dāng)再次使用時會從磁盤中讀取該二進(jìn)制文件,將 Java 對象的狀態(tài)恢復(fù)到內(nèi)存中。 
-  當(dāng)你想把內(nèi)存中的對象保存到磁盤文件或數(shù)據(jù)庫中時可以使用序列化。 
-  當(dāng)你想在網(wǎng)絡(luò)傳輸中傳送 Java 對象時,可以使用序列化。 
-  當(dāng)你想通過 RMI 傳輸對象時,可以使用序列化。 
六、注意事項
-  序列化只會保存對象的屬性狀態(tài),不會保存對象中的方法。 
-  父類實現(xiàn)了 Serializable接口,則其子類也自動實例化了該接口,也就是說子類不用顯式實現(xiàn) Serializable 接口也能參與序列化和反序列化。 
-  一個 對象 A 的實例變量引用了其他 對象 B,在 A 對象實例化的過程中 ,也會序列化 B ,前提是 A、B 兩個類都實現(xiàn)了 Serializable 接口。 
-  當(dāng)一個類實現(xiàn) Serializable 接口時,最好手動指定一個序列化版本號(serialVersionUID),避免修改源代碼后導(dǎo)致反序列化出現(xiàn)異常。 
-  當(dāng)一個類對象會被多次重復(fù)使用,且一般不會對其屬性做修改,就可以對其進(jìn)行序列化。例如數(shù)據(jù)庫操作中的實體類。 
總結(jié)
以上是生活随笔為你收集整理的Java 序列化反序列化框架比较的全部內(nèi)容,希望文章能夠幫你解決所遇到的問題。
 
                            
                        - 上一篇: vs2015 支持Android
- 下一篇: 三皇五帝分别是谁
