Java序列化方案概述 原 薦

    1、引言

    目前移動客戶端應用程序上,需要將用戶內容持久化到設備上,一般任何feed流應用,如微博、推特、新聞客戶端等都需要將內容做持久化操作,以便在內存回收後,再次進入程序能迅速恢復之前的內容。另外如一些視頻、音樂、購物等軟件,凡是收藏的視頻、歌曲、商品以及個人主頁等,也應將這些用戶私有的內容做序列化,以便無網進入時也能看到相關內容,並正常使用軟件。

    陸陸續續使用和測試過一些Java序列化方案,這篇主要從Android客戶端應用程序的角度,並以速度、序列化文件大小、實踐簡易性爲主要考慮指標介紹並對比以下序列化方案。

    2、JVM-Serializsers   

    (1)JVM-Serializsers使用介紹

    首先介紹一下JVM-Serializsers是一個很不錯的測試序列化的工具,可以用它來評測各種流行的java序列化反序列化工具,在其測試模型上構建新的序列化使用上也很方便。本文結合JVM-Serializsers測試模型,並在其測試模型下新增測試了fast-serialization的測試。本文重點介紹Java原生序列化、Kryofast-serialiationfastjsonprotocol-buffers幾種典型的序列化方案。        

    JVM-Serializsers下載源碼後,根據readme所示:

Requirements:
    - GNU Make 3.81+
    - JDK 1.5+
To compile:
      make
To run:
      ./run -help

具體步驟:

    1.切到tcp源碼目錄下

 

     2.編譯源碼:

    3.運行測試案案例:

./run -chart -include=kryo,fst-serialization,java-built-in,protobuf,hessian,json/google-gson/databind,xml/xstream+c,bson/mongodb,bson/jackson/databind,json/fastjson/databind,json/jackson/databind,thrift,avro-generic data/media.1.cks

    include參數代表要進行測試的序列化工具,當然前提是在jvm-serialization中的測試模型下已經構建完相應工具的測試用例。更多參數可通過./run -help看說明,如果帶上-chart參數,還會生成序列化性能數據的圖形對比。

    4.運行結果:

表中參數介紹:

  • Total Time (“total”):創建一個對象,將其序列化成一個字節數組,然後再反序列化成一個對象。

  • Serialization Time (“ser”):創建一個對象,將其序列化成一個字節數組。

  • Deserialization Time (“deser+deep”):相比於序列化,反序列化更耗時。爲了更公平的比較,jvm-serializers在反序列化測試時訪問了反序列化得到的對象的所有字段(也就是deep的含義),因爲部分工具反序列化時“偷懶”而沒有做足工作。

  • Serialized Size (“size”):序列化數據的大小,這個大小會依賴於使用的數據。

  • Serialization Compressed Size (“size+dfl”):使用java內置的DEFLATE(zlib)壓縮的序列化數據的大小。

  • Object Creation Time (“create”):對象創建耗時很短(平均100納秒)所以通常的比較沒什麼意義。不過,不同工具創建的對象在表現上會有不同。有的工具只是創建普通的java類,你可以直接訪問其字段,而有的使用get/set方法,有的使用builder模式。

    該工具還將這些數據通過google chart服務生成數據圖形對比圖:

圖2-1

    5.測試環境

            os:os x-10.9

            jdk:java version "1.7.0_51"

            mem:16G

            cpu: 2.3 GHz Intel Core i7

    更多在Android Runtime的測試見下文。

    以上,已經可以利用強大的JVM-Serializsers工具來分析跟構建自己想測試的序列化工具了。

    (2)Java序列化工具技術原理比較

  •  Binary Formats & language-specific ones

            JavaBuiltIn(java原生)、JavaManual(根據成員變量類型,手工寫)、FstSerliazationKryo

  •  Binary formats-generic language-unspecific ones

            ProtobufThrift、 AvroGenericHessian

  •  JSON Format 

            JacksonGsonFastJSON

  •  JSON-like:

            CKS (textual JSON-like format)、BSON(JSON-like format with extended datatypes)

            JacksonBson、MongoDB

  •  XML-based formats

            XmlXStream

    java的序列化工具大致就可以分爲以上幾類,簡單概括就分爲二進制binary和文本格式(json、xml)兩大類。

從圖2-1中可以較爲明顯的看出,在速度的對比上一般有如下規律:

   binary > textual

   language-specific > language-unspecific

   而textual中,由json相比xml冗餘度更低因此速度上更勝一籌,而json又bson這類textual serialization技術上更成熟,框架的選擇上更豐富和優秀。下面重點介紹下Kryofast-serialiationfastjsonprotocol-buffer

    3、典型Java序列化工具分析

    (1)Java原生序列化工具

     Java本身提供的序列化工具基本上能勝任大多數場景下的序列化任務,關於其序列化機制,這篇文章很細緻的解釋了,值得一讀。Java自帶的序列化工具在序列化過程中需要不僅需要將對象的完整的class name記錄下來,還需要把該類的定義也都記錄下,包括所有其他引用的類,這會是一筆很大的開銷,尤其是僅僅序列化單個對象的時候。正因爲java序列化機制會把所有meta-data記錄下來,因此當修改了類的所在的包名後,反序列化則會報錯。Java自帶序列化工具的性能問題總結如下:

  • 一個single object的序列化會遞歸地,連同所有成員變量(instsnce variables)一起序列化了,這種默認機制很容易造成不必要的序列化開銷。

  • 序列化和反序列化過程需要上面的這種機制去遞歸併用反射機制去尋找所有成員變量的信息,另外如果沒定義自己serialVersionUID的話,那麼對象及其他變量都必須自己產生一個。上述過程開銷很大。

  • 使用默認序列化機制,所有序列化類定義完整信息都會被記錄下來,包括所有包名、父類信息、以及成員變量

    (2)優化過的Java序列化工具

    kryo

    kryo根據上述Java原生序列化機制的一些問題,對了很多優化工作,而且提供了很多serializer,甚至封裝了Unsafe類型的序列化方式,更多關於Unsafe類型的序列化方式,請參考這裏,需要注意的是,jdk1.7以後,默認關閉unsafe的類(sun.misc.Unsafe)包。更多kryo介紹參考kryo的wiki,這裏貼一下kryo的典型用法。其中CompatibeFieldSerializer就是默認提供的一系列serializer的一種,顧名思義就是一種成員變量上下兼容的序列化工具,支持該類對成員變量的增刪。另外kryo更新比較活躍,問題修復很快。

private static Kryo myKryo = new Kryo();

    static {
	myKryo.register(MusicInfo.class, new CompatibleFieldSerializer<MusicInfo>(myKryo, Media.class), 50);
    }
	
    public static void saveObjectByKryo(Object o, String fileName) {
	Output output = null;
	try {
		output = new Output(new FileOutputStream(fileName), 8 * 1024);
		myKryo.writeObject(output, o);
	} catch (IOException e) {
	    e.printStackTrace();
	} finally {
	    if (output != null) {
		 output.close();
	    }
	}
    }

    public static Object readObjectByKryo(String filename, Class<?> clazz) {
	Input input = null;
	try {
	    input = new Input(new FileInputStream(new File(filename)));
	    return myKryo.readObject(input, clazz);
	} catch (Throwable t) {
	    t.printStackTrace();
	} finally {
	    if (input != null) {
            input.close();
	    }
        }
	return null;
    }

   

   fast-serialization

    fst-serialozation相對來說是一個很新的序列化工具,雖然從2-1的評測上來看,速度於kryo有一些差距,但根據本人在生產環境上的場景上測試,效果幾乎於kryo一致,都能瞬間反序列化出內容並渲染,該序列化的原理描述:

  • Fast Serialization reimplements Java Serialization with focus on speed, size and compatibility. This allows the use of FST with minimal code change.

  • FSTStructs implements a struct emulation to avoid de-/encoding completely. Use case is high performance message oriented software. Other applications are data exchange with other languages, reduction of FullGC by 'flattening' complex Objects, fast offheap, Control of data locality (CPU cache friendly) for high performance computational tasks, allocation free java programs.

  • Details: StructsIntroduction

    這裏貼一下fast-serialization的使用方法,如果原來系統使用的Java原生的序列化工具,替換成fast-serialization非常簡單:只要把Java的ObjectOutputStream與ObjectInputStream替換成FSTObjectOutput和FSTObjectInput就行了:

  • public static boolean saveObjectByJava(Object o, String filename) {
    		FSTObjectOutput oos = null;
    //		ObjectOutputStream oos = null;
    		try {
    			oos = new FSTObjectOutput(new FileOutputStream(filename));
    			oos.writeObject(o);
    			oos.flush();
    			return true;
    		} catch (IOException e) {
    			e.printStackTrace();
    		} finally {
    			if (oos != null) {
    			try {
    			    oos.close();
    			    } catch (IOException e) {
    			    e.printStackTrace();
    			    }
    			}
    		}
    		return false;
    	}
    
    	public static Object readObjectByJava(String filename) {
    //		FSTObjectInput ois = null;
    		ObjectInputStream ois = null;
    		try {
    			ois = new ObjectInputStream(new FileInputStream(filename));
    			return ois.readObject();
    		} catch (Throwable t) {
    			t.printStackTrace();
    		} finally {
    			if (ois != null) {
    				try {
    					ois.close();
    				} catch (IOException e) {
    					e.printStackTrace();
    				}
    			}
    		}
    		return null;
    	}

    (3)JSON

    從上一節的多種序列化工作的表現來看,比較優秀的JSON解析工具的表現還是比較好的,有些json解析工具甚至速度超過了一些二進制的序列化方式。Android環境下也有評測json解析性能的demo,圖3-1,可以看出jackson在速度上還是比較有優勢的,但與Android自帶的json包,也沒有數量級以上的優勢,而jackson的jar包大小達1mb多,因此對於普通的android應用來說是比較奢侈的。

    (4)Protocol-Buffer

    Protocol buffers是一個用來序列化結構化數據的技術,支持多種語言諸如C++、Java以及Python語言,可以使用該技術來持久化數據或者序列化成網絡傳輸的數據。相比較一些其他的XML技術而言,該技術的一個明顯特點就是更加節省空間(以二進制流存儲)、速度更快以及更加靈活。 通常,編寫一個protocol buffers應用需要經歷如下三步:

     1、定義消息格式文件,最好以proto作爲後綴名

     2、使用Google提供的protocol buffers編譯器來生成代碼文件,一般爲.h和.cc文件,主要是對消息格式以特定的語言方式描述

     3、使用protocol buffers庫提供的API來編寫應用程序 

    具體方法可參考google Protobuf 提供的詳細的developer guide

    總結

  • 就已有原先使用Java原生序列化方案的系統來說,kryo於fst-serializer是良好的java原生序列化方案替代者,不僅體現再編程簡單,而且速度與性能上會有大幅提升,尤其是fst-serializer ,只需替代output/inputstream 即可,性能的提升上也很可觀,目前該工具剛出來,穩定性還需要多測測。

  • 如果程序本身就用json格式序列化,則可以考慮引入一個性能優異的json解析庫,一般再服務端jackson是廣受歡迎的解析庫,但是其1.1mb的jar包大小對一般的Android應用有點奢侈,而fastjson在Android上的表現似乎沒有再JDK上那麼好,不過也比大多數解析庫快了。另外用textual格式序列化對象,在Android客戶端上還要考慮一些安全問題。

  • protobuffer更多的是一種取代xml的誇語言的消息交換格式,儘快速度很快,但是編程上需要定義消息格式,對成員變量多、業務複雜的javabean來說代價是較爲複雜的,對穩定的已有系統來說總體代價較高。

  • 下表是幾種方案的各項指標的一個對比                    

序列化工具 序列化速度 序列化文件大小 編程模型複雜度 社區活躍度 jar包大小
kryo 
極快 簡單 132kb
fst-serializer 非常簡單 246kb
protobuffer 較大 較複雜 穩定 329kb
fastjson 較快 較大 簡單 一般 338kb
jackson 一般 較大 簡單 穩定 1.1mb
gson 較慢 較大 簡單 穩定 189kb

    4、參考博客:

Java序列化機制介紹及簡單優化方法: http://www.javacodegeeks.com/2010/07/java-best-practices-high-performance.html

Java序列化“最佳”實踐: http://www.javacodegeeks.com/2010/07/java-best-practices-high-performance.html

提升Java序列化的幾種方法: http://www.javacodegeeks.com/2013/09/speed-up-with-fast-java-and-file-serialization.html

詳細的Java序列化過程: http://blog.csdn.net/zhaozheng7758/article/details/7820018

Unsafe類型的Java序列化方法:http://www.javacodegeeks.com/2012/07/native-cc-like-performance-for-java.html

protobuf介紹1:http://www.ibm.com/developerworks/cn/linux/l-cn-gpb/

protobuf介紹2:http://www.cnblogs.com/royenhome/archive/2010/10/29/1864860.html



發表評論
所有評論
還沒有人評論,想成為第一個評論的人麼? 請在上方評論欄輸入並且點擊發布.
相關文章