Java序列化工具對比
1. Java序列化工具技術原理比較
- Binary Formats & language-specific ones
JavaBuiltIn(java原生)、JavaManual(根據成員變量類型,手工寫)、FstSerliazation、Kryo - Binary formats-generic language-unspecific ones
Protobuf(Google)、Thrift(Facebook)、 AvroGeneric、Hessian - JSON Format
Jackson、Gson、FastJSON - JSON-like:
CKS (textual JSON-like format)、BSON(JSON-like format with extended datatypes)、JacksonBson、MongoDB - XML-based formats
XmlXStream
java的序列化工具大致就可以分為以上幾類,簡單概括就分為二進制binary和文本格式(json、xml)兩大類。
在速度的對比上一般有如下規律:
binary > textual
language-specific > language-unspecific
而textual中,由json相比xml冗余度更低因此速度上更勝一籌,而json又bson這類textual serialization技術上更成熟,框架的選擇上更豐富和優秀。下面重點介紹下Kryo、fast-serialiation、fastjson、protocol-buffer
2. 典型Java序列化工具分析
目前互聯網公司廣泛使用Protobuf、Thrift、Avro等成熟的序列化解決方案來搭建RPC框架,這些都是久經考驗的解決方案。
2.1 Java原生序列化工具
Java本身提供的序列化工具基本上能勝任大多數場景下的序列化任務,關於其序列化機制,這篇文章很細致的解釋了(https://blog.csdn.net/zhaozheng7758/article/details/7820018),值得一讀。Java自帶的序列化工具在序列化過程中需要不僅需要將對象的完整的class name記錄下來,還需要把該類的定義也都記錄下,包括所有其他引用的類,這會是一筆很大的開銷,尤其是僅僅序列化單個對象的時候。正因為java序列化機制會把所有meta-data記錄下來,因此當修改了類的所在的包名後,反序列化則會報錯。Java自帶序列化工具的性能問題總結如下:
一個single object的序列化會 遞歸地,連同所有成員變量(instsnce variables)一起序列化了,這種默認機制很容易造成不必要的序列化開銷。
序列化和反序列化過程需要上面的這種機制去遞歸並用反射機制去尋找所有成員變量的信息,另外如果沒定義自己serialVersionUID的話,那麽對象及其他變量都必須自己產生一個。上述過程開銷很大。
使用默認序列化機制,所有序列化類定義完整信息都會被記錄下來,包括所有包名、父類信息、以及成員變量
2.2 優化過的Java序列化工具
- kryo
kryo根據上述Java原生序列化機制的一些問題,對了很多優化工作,而且提供了很多serializer,甚至封裝了Unsafe類型的序列化方式,更多關於Unsafe類型的序列化方式,請參考這裏,需要註意的是,jdk1.7以後,默認關閉unsafe的類(sun.misc.Unsafe)包。更多kryo介紹參考kryo的wiki. - fast-serialization
fst-serialozation相對來說是一個很新的序列化工具,雖然從2-1的評測上來看,速度於kryo有一些差距,但根據本人在生產環境上的場景上測試,效果幾乎於kryo一致,都能瞬間反序列化出內容並渲染
2.3 JSON
比較優秀的JSON解析工具的表現還是比較好的,有些json解析工具甚至速度超過了一些二進制的序列化方式。
2.4 Protocol-Buffer
Protocol buffers是一個用來序列化結構化數據的技術,支持多種語言諸如C++、Java以及Python語言,可以使用該技術來持久化數據或者序列化成網絡傳輸的數據。相比較一些其他的XML技術而言,該技術的一個明顯特點就是更加節省空間(以二進制流存儲)、速度更快以及更加靈活。
另外Protobuf支持的數據類型相對較少,不支持常量類型。由於其設計的理念是純粹的展現層協議(Presentation Layer),目前並沒有一個專門支持Protobuf的RPC框架。
2.5 Thrift
Thrift是Facebook開源提供的一個高性能,輕量級RPC服務框架,其產生正是為了滿足當前大數據量、分布式、跨語言、跨平臺數據通訊的需求。 但是,Thrift並不僅僅是序列化協議,而是一個RPC框架。 相對於JSON和XML而言,Thrift在空間開銷和解析性能上有了比較大的提升,對於對性能要求比較高的分布式系統,它是一個優秀的RPC解決方案;但是由於Thrift的序列化被嵌入到Thrift框架裏面, Thrift框架本身並沒有透出序列化和反序列化接口,這導致其很難和其他傳輸層協議共同使用(例如HTTP)。
2.6 Avro
Avro解析性能高並且序列化之後的數據非常簡潔,比較適合於高性能的序列化服務。
Avro提供兩種序列化格式:JSON格式或者Binary格式。Binary格式在空間開銷和解析性能方面可以和Protobuf媲美, JSON格式方便測試階段的調試。 Avro支持的數據類型非常豐富,包括C++語言裏面的union類型。Avro支持JSON格式的IDL和類似於Thrift和Protobuf的IDL(實驗階段),這兩者之間可以互轉。Schema可以在傳輸數據的同時發送,加上JSON的自我描述屬性,這使得Avro非常適合動態類型語言。 Avro在做文件持久化的時候,一般會和Schema一起存儲,所以Avro序列化文件自身具有自我描述屬性,所以非常適合於做Hive、Pig和MapReduce的持久化數據格式。對於不同版本的Schema,在進行RPC調用的時候,服務端和客戶端可以在握手階段對Schema進行互相確認,大大提高了最終的數據解析速度。
3.下面介紹幾種常用的Java序列化技術使用示例
KryoRegister、FST、Kryo、Gson、Fastjson、JDK
3.1 JDK
public static byte[] serialize(Object obj) {
try {
ByteArrayOutputStream baos = new ByteArrayOutputStream();
ObjectOutputStream oos = new ObjectOutputStream(baos);
oos.writeObject(obj);
byte[] bs = baos.toByteArray();
baos.close();
oos.close();
return bs;
} catch (IOException e) {
throw new RuntimeException(e);
}
}
public static Object deserialize(byte[] bits) {
try {
ByteArrayInputStream bais = new ByteArrayInputStream(bits);
ObjectInputStream ois = new ObjectInputStream(bais);
Object obj = ois.readObject();
bais.close();
ois.close();
return obj;
} catch (Exception e) {
throw new RuntimeException(e);
}
}
3.2 Fastjson
一個JSON庫涉及的最基本功能就是序列化和反序列化。Fastjson支持java bean的直接序列化。 使用com.alibaba.fastjson.JSON這個類進行序列化和反序列化。
public static String serialize(Object obj){
String json = JSON.toJSONString(obj);
return json;
}
public static Object deserialize(String json, Class<?> clazz){
Object obj = JSON.parseObject(json, clazz);
return obj;
}
3.3 FST
FST fast-serialization 是重新實現的 Java 快速對象序列化的開發包。序列化速度更快(2-10倍)、體積更小,而且兼容 JDK 原生的序列化。
Java 快速序列化庫 FST 已經發布了 2.0 版本,該版本的包名已經更改,無法平滑升級。另外官方建議為了穩定性考慮還是使用最新的 1.58 版本為好
static FSTConfiguration configuration = FSTConfiguration
.createDefaultConfiguration();
public static byte[] serialize(Object obj){
return configuration.asByteArray((Serializable)obj);
}
public static Object deserialize(byte[] sec){
return configuration.asObject(sec);
}
3.4 Gson
這裏采用JSON格式同時使用采用Google的gson進行轉義.
static Gson gson = new Gson();
public static String serialize(Object obj){
String json = gson.toJson(obj);
return json;
}
public static Object deserialize(String json, Class<?> clazz){
Object obj = gson.fromJson(json, clazz);
return obj;
}
3.5 Jackson
Jackson庫(http://jackson.codehaus.org),是基於java語言的開源json格式解析工具,整個庫(使用最新的2.2版本)包含3個jar包:
jackson-core.jar——核心包(必須),提供基於“流模式”解析的API。
jackson-databind——數據綁定包(可選),提供基於“對象綁定”和“樹模型”相關API。
jackson-annotations——註解包(可選),提供註解功能。
相對於java json解析的其他庫,諸如json-lib、gson包,Jackson具有以下優點:
功能全面,提供多種模式的json解析方式,“對象綁定”使用方便,利用註解包能為我們開發提供很多便利。
性能較高,“流模式”的解析效率超過絕大多數類似的json包。
核心包:JsonPaser(json流讀取),JsonGenerator(json流輸出)。
數據綁定包:ObjectMapper(構建樹模式和對象綁定模式),JsonNode(樹節點)
public static String serialize(Object obj){
ObjectMapper mapper = new ObjectMapper();
String json = null;
try {
json = mapper.writeValueAsString(obj);
} catch (Exception e) {
// TODO Auto-generated catch block
e.printStackTrace();
}
return json;
}
public static Object deserialize(String json, Class<?> clazz){
ObjectMapper mapper = new ObjectMapper();
Object obj = null;
try {
obj = mapper.readValue(json, clazz);
} catch (Exception e) {
// TODO Auto-generated catch block
e.printStackTrace();
}
return obj;
}
3.6 Kryo 和 KryoRegister
Kryo的運行速度是java Serializable 的20倍左右
Kryo的文件大小是java Serializable的一半左右
Kryo有兩種模式:
一種是先註冊(regist),再寫對象,即writeObject函數,實際上如果不先註冊,在寫對象時也會註冊,並為class分配一個id。
註意,如果是rpc,則必須兩端都按同樣的順序註冊,否則會出錯,因為必須要明確類對應的唯一id。
另一種是寫類名及對象,即writeClassAndObject函數。
writeClassAndObject函數是先寫入(-1 + 2)(一個約定的數字),再寫入類ID(第一次要先寫-1,再寫類ID + 類名),寫入引用關系(見引用的實現),最後才寫真正的數據)。
註意每一次writeClassAndObject調用後信息都會清空,所以不用擔心和client交互時會出錯。
static Kryo kryo = new Kryo();
public static byte[] serialize(Object obj) {
byte[] buffer = new byte[2048];
Output output = new Output(buffer);
kryo.writeClassAndObject(output, obj);
byte[] bs = output.toBytes();
output.close();
return bs;
}
public static Object deserialize(byte[] src) {
Input input = new Input(src);
Object obj = kryo.readClassAndObject(input);
input.close();
return obj;
}
register
static Kryo kryo = null;
static{
kryo = new Kryo();
kryo.setReferences(false);
kryo.setRegistrationRequired(false);
kryo.setInstantiatorStrategy(new StdInstantiatorStrategy());
}
public static byte[] serialize(Object obj) {
kryo.register(obj.getClass());
byte[] buffer = new byte[2048];
Output output = new Output(buffer);
kryo.writeObject(output, obj);
byte[] bs = output.toBytes();
output.close();
return bs;
}
public static Object deserialize(byte[] src, Class<?> clazz) {
kryo.register(clazz);
Input input = new Input(src);
Object obj = kryo.readObject(input, clazz);
input.close();
return obj;
}
Object Serializalbe 優點:java原生支持,不需要提供第三方的類庫,使用比較簡單。缺點:無法跨語言,字節數占用比較大,某些情況下對於對象屬性的變化比較敏感。
對象在進行序列化和反序列化的時候,必須實現Serializable接口,但並不強制聲明唯一的serialVersionUID,是否聲明serialVersionUID對於對象序列化的向上向下的兼容性有很大的影響。
4. 小結
就已有原先使用Java原生序列化方案的系統來說,kryo於fst-serializer是良好的java原生序列化方案替代者,不僅體現再編程簡單,而且速度與性能上會有大幅提升,尤其是fst-serializer ,只需替代output/inputstream 即可,性能的提升上也很可觀,目前該工具剛出來,穩定性還需要多測測。
如果程序本身就用json格式序列化,則可以考慮引入一個性能優異的json解析庫,一般再服務端jackson是廣受歡迎的解析庫。
protobuffer更多的是一種取代xml的誇語言的消息交換格式,盡快速度很快,但是編程上需要定義消息格式,對成員變量多、業務復雜的javabean來說代價是較為復雜的,對穩定的已有系統來說總體代價較高。
下表是幾種方案的各項指標的一個對比
序列化工具 | 序列化速度 | 序列化文件大小 | 編程模型復雜度 | 社區活躍度 | jar包大小 |
---|---|---|---|---|---|
kryo | 極快 | 小 | 簡單 | 高 | 132kb |
fst-serializer | 快 | 小 | 非常簡單 | 高 | 246kb |
protobuffer | 快 | 較大 | 較復雜 | 穩定 | 329kb |
fastjson | 較快 | 較大 | 簡單 | 一般 | 338kb |
jackson | 一般 | 較大 | 簡單 | 穩定 | 1.1mb |
gson | 較慢 | 較大 | 簡單 | 穩定 | 189kb |
參考:
http://blog.51cto.com/zlfwmm/1761401
https://blog.csdn.net/wodeyuer125/article/details/44495549
https://www.javacodegeeks.com/2010/07/java-best-practices-high-performance.html
http://www.javacodegeeks.com/2010/07/java-best-practices-high-performance.html
https://blog.csdn.net/zhaozheng7758/article/details/7820018
http://www.javacodegeeks.com/2012/07/native-cc-like-performance-for-java.html
https://www.ibm.com/developerworks/cn/linux/l-cn-gpb/
Java序列化工具對比