1. 程式人生 > >物件序列化的幾種方式的比較

物件序列化的幾種方式的比較

定義一個待傳輸的物件UserVo:

Java程式碼  收藏程式碼
  1. public class UserVo{  
  2.     private String name;  
  3.     private int age;  
  4.     private long phone;  
  5.     private List<UserVo> friends;  
  6. ……  
  7. }  

 初始化UserVo的例項src:

Java程式碼  收藏程式碼
  1. UserVo src = new UserVo();  
  2. src.setName("Yaoming");  
  3. src.setAge(30);  
  4. src.setPhone(13789878978L);  
  5. UserVo f1 = new UserVo();  
  6. f1.setName("tmac");  
  7. f1.setAge(32);  
  8. f1.setPhone(138999898989L);  
  9. UserVo f2 = new UserVo();  
  10. f2.setName("liuwei");  
  11. f2.setAge(29);  
  12. f2.setPhone(138999899989L);  
  13. List<UserVo> friends = new ArrayList<UserVo>();  
  14. friends.add(f1);  
  15. friends.add(f2);  
  16. src.setFriends(friends);  

JSON格式

採用Google的gson-2.2.2.jar 進行轉義

Java程式碼  收藏程式碼
  1. Gson gson = new Gson();  
  2. String json = gson.toJson(src);  

 得到的字串:

Js程式碼  收藏程式碼
  1. {"name":"Yaoming","age":30,"phone":13789878978,"friends":[{"name":"tmac","age":32,"phone":138999898989},{"name":"liuwei","age":29,"phone":138999899989}]}  

 位元組數為153

Json的優點:明文結構一目瞭然,可以跨語言,屬性的增加減少對解析端影響較小。缺點:位元組數過多,依賴於不同的第三方類庫。

Object Serialize

UserVo實現Serializalbe介面,提供唯一的版本號:

Java程式碼  收藏程式碼
  1. public class UserVo implements Serializable{  
  2.     private static final long serialVersionUID = -5726374138698742258L;  
  3.     private String name;  
  4.     private int age;  
  5.     private long phone;  
  6.     private List<UserVo> friends;  

序列化方法:

Java程式碼  收藏程式碼
  1. ByteArrayOutputStream bos = new ByteArrayOutputStream();  
  2. ObjectOutputStream os = new ObjectOutputStream(bos);  
  3. os.writeObject(src);  
  4. os.flush();  
  5. os.close();  
  6. byte[] b = bos.toByteArray();  
  7. bos.close();  

 位元組數是238

反序列化:

Java程式碼  收藏程式碼
  1. ObjectInputStream ois = new ObjectInputStream(fis);  
  2. vo = (UserVo) ois.readObject();  
  3. ois.close();  
  4. fis.close();  

Object Serializalbe 優點:java原生支援,不需要提供第三方的類庫,使用比較簡單。缺點:無法跨語言,位元組數佔用比較大,某些情況下對於物件屬性的變化比較敏感。 

物件在進行序列化和反序列化的時候,必須實現Serializable介面,但並不強制宣告唯一的serialVersionUID

是否宣告serialVersionUID對於物件序列化的向上向下的相容性有很大的影響。我們來做個測試:

思路一

把UserVo中的serialVersionUID去掉,序列化儲存。反序列化的時候,增加或減少個欄位,看是否成功。

Java程式碼  收藏程式碼
  1. public class UserVo implements Serializable{  
  2.     private String name;  
  3.     private int age;  
  4.     private long phone;  
  5.     private List<UserVo> friends;  

儲存到檔案中:

Java程式碼  收藏程式碼
  1. ByteArrayOutputStream bos = new ByteArrayOutputStream();  
  2. ObjectOutputStream os = new ObjectOutputStream(bos);  
  3. os.writeObject(src);  
  4. os.flush();  
  5. os.close();  
  6. byte[] b = bos.toByteArray();  
  7. bos.close();  
  8. FileOutputStream fos = new FileOutputStream(dataFile);  
  9. fos.write(b);  
  10. fos.close();  

增加或者減少欄位後,從檔案中讀出來,反序列化:

Java程式碼  收藏程式碼
  1. FileInputStream fis = new FileInputStream(dataFile);  
  2. ObjectInputStream ois = new ObjectInputStream(fis);  
  3. vo = (UserVo) ois.readObject();  
  4. ois.close();  
  5. fis.close();  

結果:丟擲異常資訊

Java程式碼  收藏程式碼
  1. Exception in thread "main" java.io.InvalidClassException: serialize.obj.UserVo; local class incompatible: stream classdesc serialVersionUID = 3305402508581390189, local class serialVersionUID = 7174371419787432394  
  2.     at java.io.ObjectStreamClass.initNonProxy(ObjectStreamClass.java:560)  
  3.     at java.io.ObjectInputStream.readNonProxyDesc(ObjectInputStream.java:1582)  
  4.     at java.io.ObjectInputStream.readClassDesc(ObjectInputStream.java:1495)  
  5.     at java.io.ObjectInputStream.readOrdinaryObject(ObjectInputStream.java:1731)  
  6.     at java.io.ObjectInputStream.readObject0(ObjectInputStream.java:1328)  
  7.     at java.io.ObjectInputStream.readObject(ObjectInputStream.java:350)  
  8.     at serialize.obj.ObjectSerialize.read(ObjectSerialize.java:74)  
  9.     at serialize.obj.ObjectSerialize.main(ObjectSerialize.java:27)  
思路二

eclipse指定生成一個serialVersionUID,序列化儲存,修改欄位後反序列化

略去程式碼

結果:反序列化成功

結論

如果沒有明確指定serialVersionUID,序列化的時候會根據欄位和特定的演算法生成一個serialVersionUID,當屬性有變化時這個id發生了變化,所以反序列化的時候就會失敗。丟擲“本地classd的唯一id和流中class的唯一id不匹配”。

jdk文件關於serialVersionUID的描述:

寫道 如果可序列化類未顯式宣告 serialVersionUID,則序列化執行時將基於該類的各個方面計算該類的預設 serialVersionUID 值,如“Java(TM) 物件序列化規範”中所述。不過,強烈建議 所有可序列化類都顯式宣告 serialVersionUID 值,原因是計算預設的 serialVersionUID 對類的詳細資訊具有較高的敏感性,根據編譯器實現的不同可能千差萬別,這樣在反序列化過程中可能會導致意外的 InvalidClassException。因此,為保證 serialVersionUID 值跨不同 java 編譯器實現的一致性,序列化類必須宣告一個明確的 serialVersionUID 值。還強烈建議使用 private 修飾符顯示宣告 serialVersionUID(如果可能),原因是這種宣告僅應用於直接宣告類 -- serialVersionUID 欄位作為繼承成員沒有用處。陣列類不能宣告一個明確的 serialVersionUID,因此它們總是具有預設的計算值,但是陣列類沒有匹配 serialVersionUID 值的要求。

Google ProtoBuf

protocol buffers 是google內部得一種傳輸協議,目前專案已經開源(http://code.google.com/p/protobuf/)。它定義了一種緊湊得可擴充套件得二進位制協議格式,適合網路傳輸,並且針對多個語言有不同得版本可供選擇。

以protobuf-2.5.0rc1為例,準備工作:

下載原始碼,解壓,編譯,安裝

Shell程式碼  收藏程式碼
  1. tar zxvf protobuf-2.5.0rc1.tar.gz  
  2. ./configure  
  3. ./make  
  4. ./make install  

 測試:

Shell程式碼  收藏程式碼
  1. MacBook-Air:~ ming$ protoc --version  
  2. libprotoc 2.5.0  

 安裝成功!進入原始碼得java目錄,用mvn工具編譯生成所需得jar包,protobuf-java-2.5.0rc1.jar

1、編寫.proto檔案,命名UserVo.proto 

Text程式碼  收藏程式碼
  1. package serialize;  
  2. option java_package = "serialize";  
  3. option java_outer_classname="UserVoProtos";  
  4. message UserVo{  
  5.     optional string name = 1;  
  6.     optional int32 age = 2;  
  7.     optional int64 phone = 3;  
  8.     repeated serialize.UserVo friends = 4;  
  9. }  

2、在命令列利用protoc 工具生成builder類

Shell程式碼  收藏程式碼
  1. protoc -IPATH=.proto檔案所在得目錄 --java_out=java檔案的輸出路徑  .proto的名稱   

 得到UserVoProtos類

3、編寫序列化程式碼

Java程式碼  收藏程式碼
  1. UserVoProtos.UserVo.Builder builder = UserVoProtos.UserVo.newBuilder();  
  2. builder.setName("Yaoming");  
  3. builder.setAge(30);  
  4. builder.setPhone(13789878978L);  
  5. UserVoProtos.UserVo.Builder builder1 = UserVoProtos.UserVo.newBuilder();  
  6. builder1.setName("tmac");  
  7. builder1.setAge(32);  
  8. builder1.setPhone(138999898989L);  
  9. UserVoProtos.UserVo.Builder builder2 = UserVoProtos.UserVo.newBuilder();  
  10. builder2.setName("liuwei");  
  11. builder2.setAge(29);  
  12. builder2.setPhone(138999899989L);  
  13. builder.addFriends(builder1);  
  14. builder.addFriends(builder2);  
  15. UserVoProtos.UserVo vo = builder.build();  
  16. byte[] v = vo.toByteArray();  

 位元組數53

4、反序列化

Java程式碼  收藏程式碼
  1. UserVoProtos.UserVo uvo = UserVoProtos.UserVo.parseFrom(dstb);  
  2. System.out.println(uvo.getFriends(0).getName());  
 結果:tmac,反序列化成功

google protobuf 優點:位元組數很小,適合網路傳輸節省io,跨語言 。缺點:需要依賴於工具生成程式碼。

工作機制

proto檔案是對資料的一個描述,包括欄位名稱,型別,位元組中的位置。protoc工具讀取proto檔案生成對應builder程式碼的類庫。protoc xxxxx  --java_out=xxxxxx 生成java類庫。builder類根據自己的演算法把資料序列化成位元組流,或者把位元組流根據反射的原理反序列化成物件。官方的示例:https://developers.google.com/protocol-buffers/docs/javatutorial。

proto檔案中的欄位型別和java中的對應關係:

詳見:https://developers.google.com/protocol-buffers/docs/proto

 .proto Type  java Type  c++ Type
double  double  double
float  float  float
int32  int  int32
int64  long   int64
uint32  int  uint32
unint64  long  uint64
sint32  int  int32
sint64  long  int64
fixed32  int  uint32
fixed64  long  uint64
sfixed32  int  int32
sfixed64  long  int64
bool  boolean  bool
string  String  string
bytes  byte  string